Skip to content
This repository was archived by the owner on Nov 28, 2023. It is now read-only.

Commit 549a421

Browse files
committed
added functions
1 parent 5e0c219 commit 549a421

11 files changed

+393
-11
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@
2020
# Go workspace file
2121
go.work
2222
bin/
23-
pkg/
23+
pkg/
24+
dns-cli

cmd/common.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/BackInBash/dns-cli/internal/api"
9+
"github.com/golang-jwt/jwt/v5"
10+
)
11+
12+
func createClient() (*api.Client, error) {
13+
apiClient, err := api.NewClient(dnsApiUrl)
14+
if err != nil {
15+
return nil, fmt.Errorf("failed to create api client: %v", err)
16+
}
17+
apiClient.RequestEditors = []api.RequestEditorFn{
18+
func(ctx context.Context, req *http.Request) error {
19+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", authenticationToken))
20+
return nil
21+
},
22+
}
23+
return apiClient, nil
24+
}
25+
26+
func parseClaimFromJWT(tokenString string, claim string) (string, error) {
27+
parser := jwt.NewParser()
28+
token, _, err := parser.ParseUnverified(tokenString, &jwt.MapClaims{})
29+
if err != nil {
30+
return "", err
31+
}
32+
claims := *token.Claims.(*jwt.MapClaims)
33+
if id, ok := claims[claim]; ok {
34+
return id.(string), nil
35+
}
36+
return "", fmt.Errorf("claim %q not found in the token", claim)
37+
}

cmd/configure.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
8+
"github.com/spf13/viper"
9+
"golang.org/x/term"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var configureCmd = &cobra.Command{
15+
Use: "configure",
16+
Short: "Creates the configuration file needed to interact with the DNS API.",
17+
Long: `Creates the configuration file needed to interact with the DNS API.`,
18+
PreRun: func(cmd *cobra.Command, args []string) {
19+
// For the configure command, we cannot have the authentication token and project id set as required flags.
20+
// As that would prevent the configure command from running when no config file was written yet. As cobra does
21+
// not provide an inverse for MarkPersistentFlagRequired(), we set the required annotation on the root command
22+
// ourselves.
23+
_ = rootCmd.PersistentFlags().SetAnnotation("authentication-token", cobra.BashCompOneRequiredFlag, []string{"false"})
24+
_ = rootCmd.PersistentFlags().SetAnnotation("project-id", cobra.BashCompOneRequiredFlag, []string{"false"})
25+
},
26+
Run: func(cmd *cobra.Command, args []string) {
27+
fmt.Printf("Authentication Token [%s]: ", authenticationToken)
28+
input, err := readLongString()
29+
if err != nil {
30+
fmt.Printf("ERROR: Failed to read user input: %v\n", err)
31+
return
32+
}
33+
34+
authToken := authenticationToken
35+
input = strings.TrimSpace(input)
36+
if input != "" {
37+
authToken = input
38+
}
39+
viper.Set("authentication-token", authToken)
40+
41+
projectId, err := parseClaimFromJWT(authToken, "stackit/project/project.id")
42+
if err != nil {
43+
fmt.Printf("ERROR: Failed to parse the Authentication Token: %v\n", err)
44+
return
45+
}
46+
viper.Set("project-id", projectId)
47+
48+
// Viper has a known issue with WriteConfig running into an error in case the config file does not exist. Therefore,
49+
// we first try SafeWriteConfig which only works in cases where the config file does not exist. If that
50+
// fails, the config file is probably already there, and we use WriteConfig.
51+
if err := viper.SafeWriteConfig(); err != nil {
52+
if err := viper.WriteConfig(); err != nil {
53+
fmt.Printf("ERROR: Failed to write config: %v\n", err)
54+
return
55+
}
56+
}
57+
fmt.Println("Configuration successfully written")
58+
},
59+
}
60+
61+
func init() {
62+
rootCmd.AddCommand(configureCmd)
63+
}
64+
65+
// readLongString provides a way to read more than 1024 characters from the terminal by switching the terminal into
66+
// raw mode. Otherwise, long strings like the authentication token would be truncated to 1024 characters because of
67+
// canonical input mode for terminals.
68+
func readLongString() (string, error) {
69+
termStateBackup, _ := term.MakeRaw(int(os.Stdin.Fd()))
70+
defer term.Restore(int(os.Stdin.Fd()), termStateBackup) // nolint:errcheck
71+
72+
result := ""
73+
characters := make([]byte, 1024)
74+
for {
75+
n, err := os.Stdin.Read(characters)
76+
if err != nil {
77+
return result, err
78+
}
79+
for i := 0; i < n; i++ {
80+
if characters[i] == 0x03 || // Ctrl+C
81+
characters[i] == 0x0d { // Enter
82+
fmt.Print("\r\n")
83+
return result, nil
84+
}
85+
fmt.Printf("%s", string(characters[i]))
86+
result = result + string(characters[i])
87+
}
88+
}
89+
}

cmd/create.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
var createCmd = &cobra.Command{
8+
Use: "create",
9+
Short: "Creates, updates and deletes resources for DNS.",
10+
Long: `Creates, updates and deletes resources for DNS.`,
11+
}
12+
13+
func init() {
14+
rootCmd.AddCommand(createCmd)
15+
}

cmd/createZone.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
9+
"github.com/BackInBash/dns-cli/internal/api"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var (
14+
createZoneName string
15+
createDNSZoneName string
16+
)
17+
18+
var createZoneCmd = &cobra.Command{
19+
Use: "zone",
20+
Short: "Creates a new DNS Zone.",
21+
Long: `Creates a new DNS Zone.`,
22+
Run: func(cmd *cobra.Command, args []string) {
23+
if err := createZone(); err != nil {
24+
fmt.Printf("ERROR: %v\n", err)
25+
}
26+
},
27+
}
28+
29+
func init() {
30+
createCmd.AddCommand(createZoneCmd)
31+
32+
createZoneCmd.PersistentFlags().StringVar(&createZoneName, "name", "", "The name to set for the instance.")
33+
_ = createZoneCmd.MarkPersistentFlagRequired("name")
34+
35+
createZoneCmd.PersistentFlags().StringVar(&createDNSZoneName, "dns-name", "", "The Domain DNS Name.")
36+
_ = createZoneCmd.MarkPersistentFlagRequired("dns-name")
37+
}
38+
39+
func createZone() error {
40+
client, err := createClient()
41+
if err != nil {
42+
return err
43+
}
44+
request := api.PostV1ProjectsProjectIdZonesJSONRequestBody{
45+
// DnsName zone name
46+
DnsName: createDNSZoneName,
47+
48+
// Name user given name
49+
Name: createZoneName,
50+
}
51+
response, err := client.PostV1ProjectsProjectIdZones(context.Background(), projectId, request)
52+
if err != nil {
53+
return fmt.Errorf("failed to create instance: %w", err)
54+
}
55+
if response.StatusCode != http.StatusAccepted {
56+
return fmt.Errorf("unexpected status code: %s", response.Status)
57+
}
58+
59+
var instance api.ZoneResponseZone
60+
if err := json.NewDecoder(response.Body).Decode(&instance); err != nil {
61+
return fmt.Errorf("failed to decode response: %w", err)
62+
}
63+
if err := printZone(instance.Zone); err != nil {
64+
return err
65+
}
66+
return nil
67+
}

cmd/get.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
var getCmd = &cobra.Command{
8+
Use: "get",
9+
Short: "Returns resources from DNS Zones.",
10+
Long: `Returns resources from one or more DNS Zones.`,
11+
}
12+
13+
func init() {
14+
rootCmd.AddCommand(getCmd)
15+
}

cmd/getZone.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"os"
9+
"text/tabwriter"
10+
11+
"github.com/BackInBash/dns-cli/internal/api"
12+
13+
"github.com/spf13/cobra"
14+
)
15+
16+
var (
17+
getZoneId string
18+
)
19+
20+
var getZoneCmd = &cobra.Command{
21+
Use: "zone",
22+
Short: "Returns a Zone from the DNS.",
23+
Long: `Returns a Zone from the DNS.`,
24+
Run: func(cmd *cobra.Command, args []string) {
25+
if err := getZone(); err != nil {
26+
fmt.Printf("ERROR: %v\n", err)
27+
}
28+
},
29+
}
30+
31+
func init() {
32+
getCmd.AddCommand(getZoneCmd)
33+
34+
getZoneCmd.PersistentFlags().StringVar(&getZoneId, "zone-id", "", "The UUID of the DNS Zone.")
35+
_ = getZoneCmd.MarkPersistentFlagRequired("zone-id")
36+
}
37+
38+
func getZone() error {
39+
client, err := createClient()
40+
if err != nil {
41+
return err
42+
}
43+
response, err := client.GetV1ProjectsProjectIdZonesZoneId(context.Background(), projectId, getZoneId)
44+
if err != nil {
45+
return fmt.Errorf("failed to get zone: %w", err)
46+
}
47+
if response.StatusCode != http.StatusOK {
48+
return fmt.Errorf("unexpected status code: %s", response.Status)
49+
}
50+
51+
var zone api.ZoneResponseZone
52+
if err := json.NewDecoder(response.Body).Decode(&zone); err != nil {
53+
return fmt.Errorf("failed to decode response: %w", err)
54+
}
55+
if err := printZone(zone.Zone); err != nil {
56+
return err
57+
}
58+
return nil
59+
}
60+
61+
func printZone(zone api.DomainZone) error {
62+
writer := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
63+
defer writer.Flush()
64+
_, err := fmt.Fprintf(writer, "Id\tName\tDescription\tActive\n")
65+
if err != nil {
66+
return fmt.Errorf("failed to write to tabwriter: %w", err)
67+
}
68+
_, err = fmt.Fprintf(writer, "%s\t%s\t%s\t%t\n", zone.Id, zone.Name, zone.Description, zone.Active)
69+
if err != nil {
70+
return fmt.Errorf("failed to write to tabwriter: %w", err)
71+
}
72+
return nil
73+
}

cmd/getZones.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"os"
9+
"text/tabwriter"
10+
11+
"github.com/BackInBash/dns-cli/internal/api"
12+
13+
"github.com/spf13/cobra"
14+
)
15+
16+
var getZonesCmd = &cobra.Command{
17+
Use: "zones",
18+
Short: "Returns all Zones from the DNS.",
19+
Long: `Returns all Zones from the DNS.`,
20+
Run: func(cmd *cobra.Command, args []string) {
21+
if err := getZone(); err != nil {
22+
fmt.Printf("ERROR: %v\n", err)
23+
}
24+
},
25+
}
26+
27+
func init() {
28+
getCmd.AddCommand(getZonesCmd)
29+
}
30+
31+
func getZones() error {
32+
client, err := createClient()
33+
if err != nil {
34+
return err
35+
}
36+
37+
size := int(100)
38+
start := int(1)
39+
empty := string("")
40+
parameters := api.GetV1ProjectsProjectIdZonesParams{
41+
PageSize: &size,
42+
Page: &start,
43+
DnsNameEq: &empty,
44+
DnsNameLike: &empty,
45+
}
46+
47+
response, err := client.GetV1ProjectsProjectIdZones(context.Background(), projectId, &parameters)
48+
if err != nil {
49+
return fmt.Errorf("failed to get zones: %w", err)
50+
}
51+
if response.StatusCode != http.StatusOK {
52+
return fmt.Errorf("unexpected status code: %s", response.Status)
53+
}
54+
55+
var zone api.ZoneResponseZoneAll
56+
if err := json.NewDecoder(response.Body).Decode(&zone); err != nil {
57+
return fmt.Errorf("failed to decode response: %w", err)
58+
}
59+
if err := printZones(zone.Zones); err != nil {
60+
return err
61+
}
62+
return nil
63+
}
64+
65+
func printZones(zones []api.DomainZone) error {
66+
writer := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
67+
defer writer.Flush()
68+
_, err := fmt.Fprintf(writer, "Id\tName\tDescription\tActive\n")
69+
if err != nil {
70+
return fmt.Errorf("failed to write to tabwriter: %w", err)
71+
}
72+
for _, zone := range zones {
73+
_, err = fmt.Fprintf(writer, "%s\t%s\t%s\t%t\n", zone.Id, zone.Name, zone.Description, zone.Active)
74+
if err != nil {
75+
return fmt.Errorf("failed to write to tabwriter: %w", err)
76+
}
77+
}
78+
return nil
79+
}

0 commit comments

Comments
 (0)