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
60 changes: 44 additions & 16 deletions cmd/argocd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package commands
import (
"context"
"fmt"
"log"
"net/url"
"os"
"text/tabwriter"

Expand Down Expand Up @@ -41,31 +43,57 @@ func NewApplicationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
// NewApplicationAddCommand returns a new instance of an `argocd app add` command
func NewApplicationAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
fileURL string
repoURL string
appPath string
appName string
env string
destServer string
destNamespace string
)
var command = &cobra.Command{
Use: "add",
Short: fmt.Sprintf("%s app add APPNAME", cliName),
Short: fmt.Sprintf("%s app add", cliName),
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
if len(args) != 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
app := argoappv1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: args[0],
},
Spec: argoappv1.ApplicationSpec{
Source: argoappv1.ApplicationSource{
RepoURL: repoURL,
Path: appPath,
Environment: env,
var app argoappv1.Application
if fileURL != "" {
var (
fileContents []byte
err error
)
_, err = url.ParseRequestURI(fileURL)
if err != nil {
fileContents, err = readLocalFile(fileURL)
} else {
fileContents, err = readRemoteFile(fileURL)
}
if err != nil {
log.Fatal(err)
}
unmarshalApplication(fileContents, &app)

} else {
// all these params are required if we're here
if repoURL == "" || appPath == "" || appName == "" {
c.HelpFunc()(c, args)
os.Exit(1)
}
app = argoappv1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
},
},
Spec: argoappv1.ApplicationSpec{
Source: argoappv1.ApplicationSource{
RepoURL: repoURL,
Path: appPath,
Environment: env,
},
},
}
}
if destServer != "" || destNamespace != "" {
app.Spec.Destination = &argoappv1.ApplicationDestination{
Expand All @@ -79,10 +107,10 @@ func NewApplicationAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
errors.CheckError(err)
},
}
command.Flags().StringVar(&repoURL, "repo", "", "Repository URL")
errors.CheckError(command.MarkFlagRequired("repo"))
command.Flags().StringVar(&appPath, "path", "", "Path in repository to the ksonnet app directory")
errors.CheckError(command.MarkFlagRequired("path"))
command.Flags().StringVarP(&fileURL, "file", "f", "", "Filename or URL to Kubernetes manifests for the app")
command.Flags().StringVar(&appName, "name", "", "A name for the app, ignored if a file is set")
command.Flags().StringVar(&repoURL, "repo", "", "Repository URL, ignored if a file is set")
command.Flags().StringVar(&appPath, "path", "", "Path in repository to the ksonnet app directory, ignored if a file is set")
command.Flags().StringVar(&env, "env", "", "Application environment to monitor")
command.Flags().StringVar(&destServer, "dest-server", "", "K8s cluster URL (overrides the server URL specified in the ksonnet app.yaml)")
command.Flags().StringVar(&destNamespace, "dest-namespace", "", "K8s target namespace (overrides the namespace specified in the ksonnet app.yaml)")
Expand Down
49 changes: 49 additions & 0 deletions cmd/argocd/commands/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package commands

import (
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicking. Please run goimports -w cmd/argocd/commands/util.go. We usually automatically run goimports on file save, so next person might get unexpected changes.

"encoding/json"
"io/ioutil"
"log"
"net/http"

argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/ghodss/yaml"
)

// unmarshalApplication tries to convert a YAML or JSON byte array into an Application struct.
func unmarshalApplication(data []byte, app *argoappv1.Application) {
// first, try unmarshaling as JSON
// Based on technique from Kubectl, which supports both YAML and JSON:
// https://mlafeldt.github.io/blog/teaching-go-programs-to-love-json-and-yaml/
// http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/
// Short version: JSON unmarshaling won't zero out null fields; YAML unmarshaling will.
// This may have unintended effects or hard-to-catch issues when populating our application object.
data, err := yaml.YAMLToJSON(data)
if err != nil {
log.Fatal("Could not decode valid JSON or YAML Kubernetes manifest")
}
err = json.Unmarshal(data, &app)
if err != nil {
log.Fatalf("Could not unmarshal Kubernetes manifest: %s", string(data))
}
}

// readLocalFile reads a file from disk and returns its contents as a byte array.
// The caller is responsible for checking error return values.
func readLocalFile(path string) (data []byte, err error) {
data, err = ioutil.ReadFile(path)
return
}

// readRemoteFile issues a GET request to retrieve the contents of the specified URL as a byte array.
// The caller is responsible for checking error return values.
func readRemoteFile(url string) (data []byte, err error) {
resp, err := http.Get(url)
if err == nil {
defer func() {
_ = resp.Body.Close()
}()
data, err = ioutil.ReadAll(resp.Body)
}
return
}
64 changes: 64 additions & 0 deletions cmd/argocd/commands/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package commands

import (
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"testing"
)

func TestReadLocalFile(t *testing.T) {
sentinel := "Hello, world!"

file, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
panic(err)
}
defer func() {
_ = os.Remove(file.Name())
}()

_, _ = file.WriteString(sentinel)
_ = file.Sync()

data, err := readLocalFile(file.Name())
if string(data) != sentinel {
t.Errorf("Test data did not match (err = %v)! Expected \"%s\" and received \"%s\"", err, sentinel, string(data))
}
}

func TestReadRemoteFile(t *testing.T) {
sentinel := "Hello, world!"

serve := func(c chan<- string) {
// listen on first available dynamic (unprivileged) port
listener, err := net.Listen("tcp", ":0")
if err != nil {
panic(err)
}

// send back the address so that it can be used
c <- listener.Addr().String()

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// return the sentinel text at root URL
fmt.Fprint(w, sentinel)
})

panic(http.Serve(listener, nil))
}

c := make(chan string, 1)

// run a local webserver to test data retrieval
go serve(c)

address := <-c
data, err := readRemoteFile("http://" + address)
t.Logf("Listening at address: %s", address)
if string(data) != sentinel {
t.Errorf("Test data did not match (err = %v)! Expected \"%s\" and received \"%s\"", err, sentinel, string(data))
}
}