Skip to content

Commit

Permalink
add cdn operations (#460)
Browse files Browse the repository at this point in the history
* add cdn operations

* add docs

* add tests
fix doc
fix flags for update

* remove unused constant

* fix default url

* update docs

* remove wrong constant string

* fix typo in file name

* add example

* update filter names and split geo restrictions

* update docs
  • Loading branch information
digna-ionos authored Oct 30, 2024
1 parent ceb6cf4 commit d979378
Show file tree
Hide file tree
Showing 55 changed files with 8,869 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## [v6.8.0]
- Added support for CDN operations

## [v6.7.8] (October 2024)

### Added
Expand Down
19 changes: 19 additions & 0 deletions commands/cdn/cdn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package cdn

import (
"github.com/ionos-cloud/ionosctl/v6/commands/cdn/distribution"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/spf13/cobra"
)

func Command() *core.Command {
cmd := &core.Command{
Command: &cobra.Command{
Use: "cdn",
Short: "The sub-commands of the 'cdn' resource help manage CDN distributions",
TraverseChildren: true,
},
}
cmd.AddCommand(distribution.Command())
return cmd
}
37 changes: 37 additions & 0 deletions commands/cdn/completer/completer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package completer

import (
"context"
"github.com/ionos-cloud/ionosctl/v6/internal/client"
"github.com/ionos-cloud/ionosctl/v6/pkg/functional"
cdn "github.com/ionos-cloud/sdk-go-cdn"
)

// DistributionsProperty returns a list of properties of all distributions matching the given filters
func DistributionsProperty[V any](f func(cdn.Distribution) V, fs ...Filter) []V {
recs, err := Distributions(fs...)
if err != nil {
return nil
}
return functional.Map(*recs.Items, f)
}

// Distributions returns all distributions matching the given filters
func Distributions(fs ...Filter) (cdn.Distributions, error) {
req := client.Must().CDNClient.DistributionsApi.DistributionsGet(context.Background())
for _, f := range fs {
var err error
req, err = f(req)
if err != nil {
return cdn.Distributions{}, err
}
}

ls, _, err := req.Execute()
if err != nil {
return cdn.Distributions{}, err
}
return ls, nil
}

type Filter func(request cdn.ApiDistributionsGetRequest) (cdn.ApiDistributionsGetRequest, error)
150 changes: 150 additions & 0 deletions commands/cdn/distribution/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package distribution

import (
"context"
"encoding/json"
"fmt"
"github.com/ionos-cloud/ionosctl/v6/internal/client"
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/ionos-cloud/ionosctl/v6/pkg/pointer"
"github.com/ionos-cloud/ionosctl/v6/pkg/uuidgen"
cdn "github.com/ionos-cloud/sdk-go-cdn"
"github.com/spf13/viper"
"os"
)

func Create() *core.Command {
cmd := core.NewCommand(context.Background(), nil, core.CommandBuilder{
Namespace: "cdn",
Resource: "distribution",
Verb: "create",
Aliases: []string{"c", "post"},
ShortDesc: "Create a CDN distribution. Wiki: https://docs.ionos.com/cloud/network-services/cdn/dcd-how-tos/create-cdn-distribution",
Example: "ionosctl cdn ds create --domain foo-bar.com --certificate-id id --routing-rules rules.json",
PreCmdRun: func(c *core.PreCommandConfig) error {
if err := core.CheckRequiredFlagsSets(c.Command, c.NS,
[]string{constants.FlagCDNDistributionDomain, constants.FlagCDNDistributionRoutingRules},
[]string{constants.FlagCDNDistributionRoutingRulesExample}); err != nil {
return err
}

return nil
},
CmdRun: func(c *core.CommandConfig) error {
if viper.GetBool(core.GetFlagName(c.NS, constants.FlagCDNDistributionRoutingRulesExample)) {
fmt.Fprintf(c.Command.Command.OutOrStdout(), RoutingRuleExample)
return nil
}

input := &cdn.DistributionProperties{}
if err := setPropertiesFromFlags(c, input); err != nil {
return err
}

id := uuidgen.Must()
res, _, err := client.Must().CDNClient.DistributionsApi.DistributionsPut(context.Background(), id).
DistributionUpdate(cdn.DistributionUpdate{
Id: &id,
Properties: input,
}).Execute()
if err != nil {
return err
}

return printDistribution(c, res)
},
InitClient: true,
})

cmd.Command.SilenceUsage = true
cmd.Command.Flags().SortFlags = false
return addDistributionCreateFlags(cmd)
}

func addDistributionCreateFlags(cmd *core.Command) *core.Command {
cmd.AddStringFlag(constants.FlagCDNDistributionDomain, "", "", "The domain of the distribution")
cmd.AddStringFlag(constants.FlagCDNDistributionCertificateID, "", "", "The ID of the certificate")
cmd.AddStringFlag(constants.FlagCDNDistributionRoutingRules, "", "", "The routing rules of the distribution. JSON string or file path of routing rules")
cmd.AddBoolFlag(constants.FlagCDNDistributionRoutingRulesExample, "", false, "Print an example of routing rules")
return cmd
}

func setPropertiesFromFlags(c *core.CommandConfig, p *cdn.DistributionProperties) error {
if fn := core.GetFlagName(c.NS, constants.FlagCDNDistributionDomain); viper.IsSet(fn) {
p.Domain = pointer.From(viper.GetString(fn))
}

if fn := core.GetFlagName(c.NS, constants.FlagCDNDistributionCertificateID); viper.IsSet(fn) {
p.CertificateId = pointer.From(viper.GetString(fn))
}

if fn := core.GetFlagName(c.NS, constants.FlagCDNDistributionRoutingRules); viper.IsSet(fn) {
rr := viper.GetString(fn)
data, err := getRoutingRulesData(rr)
if err != nil {
return fmt.Errorf("error reading routing rules file: %s", err)
}

rules, err := getRoutingRulesFromJSON(data)
if err != nil {
return fmt.Errorf("error parsing routing rules: %s", err)
}
p.RoutingRules = rules
}

return nil
}

func getRoutingRulesFromJSON(data []byte) (*[]cdn.RoutingRule, error) {
var rr []cdn.RoutingRule
err := json.Unmarshal(data, &rr)
if err != nil {
return nil, err
}
return &rr, nil
}

func getRoutingRulesData(input string) ([]byte, error) {
switch _, err := os.Stat(input); {
case err == nil:
return os.ReadFile(input)
case os.IsNotExist(err):
return []byte(input), nil
default:
return nil, err
}
}

const RoutingRuleExample = `
[
{
"prefix": "/api",
"scheme": "http/https",
"upstream": {
"caching": true,
"geoRestrictions": {
"allowList": ["CN", "RU"]
},
"host": "clitest.example.com",
"rateLimitClass": "R500",
"sniMode": "distribution",
"waf": true
}
},
{
"prefix": "/api2",
"scheme": "http/https",
"upstream": {
"caching": false,
"geoRestrictions": {
"blockList": ["CN", "RU"]
},
"host": "server2.example.com",
"rateLimitClass": "R10",
"sniMode": "origin",
"waf": false
}
}
]
`
62 changes: 62 additions & 0 deletions commands/cdn/distribution/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package distribution

import (
"context"
"fmt"
"github.com/ionos-cloud/ionosctl/v6/commands/cdn/completer"
"github.com/ionos-cloud/ionosctl/v6/internal/client"
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
"github.com/ionos-cloud/ionosctl/v6/pkg/confirm"
cdn "github.com/ionos-cloud/sdk-go-cdn"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/ionos-cloud/ionosctl/v6/internal/core"
)

func Delete() *core.Command {
cmd := core.NewCommand(context.Background(), nil, core.CommandBuilder{
Namespace: "cdn",
Resource: "distribution",
Verb: "delete",
Aliases: []string{"del", "d"},
ShortDesc: "Delete a distribution",
Example: `ionosctl cdn ds delete --distribution-id ID`,
PreCmdRun: func(c *core.PreCommandConfig) error {
if err := core.CheckRequiredFlags(c.Command, c.NS, constants.FlagCDNDistributionID); err != nil {
return err
}

return nil
},
CmdRun: func(c *core.CommandConfig) error {
distributionID := viper.GetString(core.GetFlagName(c.NS, constants.FlagCDNDistributionID))
d, _, err := client.Must().CDNClient.DistributionsApi.DistributionsFindById(context.Background(), distributionID).Execute()
if err != nil {
return fmt.Errorf("distribution not found: %w", err)
}

yes := confirm.FAsk(c.Command.Command.InOrStdin(), fmt.Sprintf("Are you sure you want to delete distribution %s for domain %s", *d.Id, *d.Properties.Domain),
viper.GetBool(constants.ArgForce))
if !yes {
return fmt.Errorf("user cancelled deletion")
}

_, err = client.Must().CDNClient.DistributionsApi.DistributionsDelete(context.Background(), *d.Id).Execute()
return err
},
InitClient: true,
})

cmd.AddStringFlag(constants.FlagCDNDistributionID, constants.FlagIdShort, "", "The ID of the distribution you want to retrieve", core.RequiredFlagOption())
_ = cmd.Command.RegisterFlagCompletionFunc(constants.FlagCDNDistributionID, func(c *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completer.DistributionsProperty(func(r cdn.Distribution) string {
return *r.Id
}), cobra.ShellCompDirectiveNoFileComp
})

cmd.Command.SilenceUsage = true
cmd.Command.Flags().SortFlags = false

return cmd
}
37 changes: 37 additions & 0 deletions commands/cdn/distribution/distribution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package distribution

import (
"github.com/ionos-cloud/ionosctl/v6/commands/cdn/distribution/routingrules"
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/tabheaders"
"github.com/spf13/cobra"
)

var (
allCols = []string{"Id", "Domain", "CertificateId", "State"}
defaultCols = []string{"Id", "Domain", "CertificateId", "State"}
)

func Command() *core.Command {
cmd := &core.Command{
Command: &cobra.Command{
Use: "distribution",
Short: "The sub-commands of 'ionosctl cdn distribution' allow you to manage CDN distributions",
Aliases: []string{"ds"},
TraverseChildren: true,
},
}
cmd.Command.PersistentFlags().StringSlice(constants.ArgCols, nil, tabheaders.ColsMessage(allCols))
_ = cmd.Command.RegisterFlagCompletionFunc(constants.ArgCols, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return allCols, cobra.ShellCompDirectiveNoFileComp
})

cmd.AddCommand(List())
cmd.AddCommand(FindByID())
cmd.AddCommand(Delete())
cmd.AddCommand(Create())
cmd.AddCommand(Update())
cmd.AddCommand(routingrules.Root())
return cmd
}
64 changes: 64 additions & 0 deletions commands/cdn/distribution/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package distribution

import (
"context"
"fmt"
"github.com/ionos-cloud/ionosctl/v6/commands/cdn/completer"
cdn "github.com/ionos-cloud/sdk-go-cdn"
"github.com/spf13/cobra"

"github.com/ionos-cloud/ionosctl/v6/internal/client"
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
"github.com/ionos-cloud/ionosctl/v6/internal/core"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/json2table/jsonpaths"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/jsontabwriter"
"github.com/ionos-cloud/ionosctl/v6/internal/printer/tabheaders"
"github.com/spf13/viper"
)

func FindByID() *core.Command {
cmd := core.NewCommand(context.Background(), nil, core.CommandBuilder{
Namespace: "cdn",
Resource: "distribution",
Verb: "get",
Aliases: []string{"g"},
ShortDesc: "Retrieve a distribution",
Example: "ionosctl cdn ds get --distribution-id ID",
PreCmdRun: func(c *core.PreCommandConfig) error {
if err := core.CheckRequiredFlags(c.Command, c.NS, constants.FlagCDNDistributionID); err != nil {
return err
}

return nil
},
CmdRun: func(c *core.CommandConfig) error {
distributionID := viper.GetString(core.GetFlagName(c.NS, constants.FlagCDNDistributionID))
r, _, err := client.Must().CDNClient.DistributionsApi.DistributionsFindById(context.Background(),
distributionID,
).Execute()
if err != nil {
return err
}

cols, _ := c.Command.Command.Flags().GetStringSlice(constants.ArgCols)
out, err := jsontabwriter.GenerateOutput("", jsonpaths.CDNDistribution, r,
tabheaders.GetHeadersAllDefault(defaultCols, cols))
if err != nil {
return err
}

fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
return nil
},
InitClient: true,
})
cmd.AddStringFlag(constants.FlagCDNDistributionID, constants.FlagIdShort, "", "The ID of the distribution you want to retrieve", core.RequiredFlagOption())
_ = cmd.Command.RegisterFlagCompletionFunc(constants.FlagCDNDistributionID, func(c *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completer.DistributionsProperty(func(r cdn.Distribution) string {
return *r.Id
}), cobra.ShellCompDirectiveNoFileComp
})
cmd.Command.SilenceUsage = true
cmd.Command.Flags().SortFlags = false
return cmd
}
Loading

0 comments on commit d979378

Please sign in to comment.