Creating a command Cli with Golang to clone repositories from AWS CodeCommit
My Problem
I’m starting in the AWS world and right now in the project I’m working we need to clone many repositories from AWS CodeCommit, to clone any repo we need to write something like that:
git clone “https://username:pass@git-codecommit.us-east-1.amazonaws.com/v1/repos/demo-with-super-extense-name
but the username and password need to be URL encoded before cloning the repo, so it was so boring, really I need to do something like this
- Url encode the username
- Url encode the password
- Cut the URL just using the way I need
- Create the
https://username:pass@git-codecommit.us-east-1.amazonaws.com/v1/repos/demo-with-super-extense-name
Just before cloning a repository? So I decided to create my script to clone all the repositories I want from AWS CodeCommit just writing something like this:
cloneaws clone —profile=myProfile —url=“ssh-Url-or-https-Url” —projectName=demo
This is more friendly and so easy without URL encode first the username and password
Before start, I had months trying to learn Go, because I feel empathy with the pet of the language (just kidding I just want to learn another language, but the pet is so cute), so I decide to write a command CLI. with Go
Solution
Well, the principal idea is clone a repository from AWS CodeCommit without writing the user and password, just set a label to find in a list of credentials.
First, we create a subcommand with the code below:
cloneAwsCommand := flag.NewFlagSet("clone", flag.ExitOnError)
Add the few parameters needed by the command with the next code:
cloneUrl := cloneAwsCommand.String("url", "", "Repository HTTPS/SSH URL (Required)")
cloneProfile := cloneAwsCommand.String("profile", "", "Profile name to use cloning repository (Required)")
cloneProjectName := cloneAwsCommand.String("projectName", "", "Folder name to copy repo (optional)")
cloneHelp := cloneAwsCommand.Bool("help", false, "Show usage for subcommand")
Validate if has set any subcommand:
if len(os.Args) < 2 {
log.Fatalln("Subcommand is required")
}
Now I'll check what subcommand to use, for now just has clone
but I pretend to add many other functionalities like "add a new profile, modified profile"
To check the subcommand we use a switch with the bode below:
switch os.Args[1] {
case "clone":
// Clone block
default:
fmt.Println(" clone string")
fmt.Println(" Subcommand for clone")
log.Fatalln("Subcommand is required")
}
the switch has a default, this will print in console an error with the message "subcommand is required".
Now I want to explain the code in the clone block:
// First we parse the arguments for clone subcommand.
cloneAwsCommand.Parse(os.Args[2:])
// Validate if set the flag "help" to display the clone subcommand usage.
if *cloneHelp == true {
cloneAwsCommand.Usage()
os.Exit(1)
}
/*
Validate if the profile and URL flag is an empty string.
If one of them is empty print default values.
*/
if *cloneProfile == "" || *cloneUrl == "" {
cloneAwsCommand.PrintDefaults()
log.Fatalln("No flags set for clone subcommand")
}
//Validate again if profile is an empty string.
if *cloneProfile == "" {
cloneAwsCommand.PrintDefaults()
log.Fatalln("No profile value to find")
}
/*
Call findCredentials function to get the username and password.
I'll explain this function next.
*/
credentials := findCredentials(*cloneProfile)
// Validate again if clone URL is and empty string.
if *cloneUrl == "" {
cloneAwsCommand.PrintDefaults()
log.Fatalln("")
}
// Get the URL substring.
urlRepo := getRepoPath(*cloneUrl)
// URL encode the email and password from credentials.
userEncode := url.QueryEscape(credentials.Email)
passwordEncode := url.QueryEscape(credentials.Password)
// Set the URL to clone
fullUrlEncode := "https://" + userEncode + ":" + passwordEncode + "@" + urlRepo
/*
Execute the clone command.
I'll explain this function next.
*/
executeCloneCommand(fullUrlEncode, *cloneProjectName)
Before creating the function we will create a Struct with the structure of a user
and Users
what is an array of many users.
// Users struct, Array of users.
type Users struct {
Users []User `json:"users"`
}
// User struct, JSON with attributes.
type User struct {
Alias string `json:"alias"`
Email string `json:"email"`
Password string `json:"password"`
}
Now I'll explain the function findCredentials
/*
Create a function, this receives one parameter:
* alias - String
The function will return a User struct.
*/
func findCredentials(alias string) User {
// Get the current user, the user who call the command.
user, err := user.Current()
// create the credentials file path.
credentialsPath := user.HomeDir + "/.codecommit/" + "credentials.json"
// Open the credentials file.
jsonFile, err := os.Open(credentialsPath)
// Validate if hasn't error opening the file.
if err != nil {
log.Fatalln(err)
}
defer jsonFile.Close()
// Read the file.
byteValue, _ := ioutil.ReadAll(jsonFile)
// Set a variable using the Users Struct.
var users Users
json.Unmarshal(byteValue, &users)
// Make a for to find the right alias in the file.
for i := 0; i < len(users.Users); i++ {
if alias == users.Users[i].Alias {
return users.Users[i]
}
}
// If no exist the profile, send an error indicating what to do for add a new profile.
fmt.Println("No profile with the value:", alias)
log.Fatalln("Please add the user/password/alias in the credentials.json file inside" + user.HomeDir + "/.codecommit folder")
return User{}
}
The last step is executeCloneCommand
and I'll explain how to do this below
/*
Create a function, this receives two parameter:
* URL - String
* projectName - String
*/
func executeCloneCommand(url string, projectName string) {
/*
Validate if the parameter "projectName" is a empty to set the parameter and execute the comand `git clone` else execute the gommand `git clone`without this patameter.
*/
if projectName != "" {
/*
Execute the command with the project name, this will execute a command like this:
`git clone https://url test`
If executing the command has an error print it in console an exit the program.
*/
if err := exec.Command("git", "clone", url, projectName).Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
} else {
/*
Execute the command without project name, this will execute a command like this:
`git clone https://url test`
If executing the command has an error print it in console an exit the program.
*/
if err := exec.Command("git", "clone", url).Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
}
After writing all this you can test the command using go run main.go clone --profile="your-profile" --url="https://url" --projectName="test"
and clone any project you have in AWS CodeCommit.
However if you want install the command go to root project and execute go install
and now can clone from AWS CodeCommit using cloneaws clone --profile="your-profile" --url="https://url" --projectName="test"
Top comments (0)