setup.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package commands
  2. import (
  3. "fmt"
  4. "os"
  5. "reflect"
  6. "strings"
  7. "github.com/aws/aws-sdk-go/aws/awserr"
  8. "github.com/aws/aws-sdk-go/aws/credentials"
  9. "github.com/aws/aws-sdk-go/aws/defaults"
  10. contextStore "github.com/docker/ecs-plugin/pkg/docker"
  11. "github.com/manifoldco/promptui"
  12. "github.com/spf13/cobra"
  13. "gopkg.in/ini.v1"
  14. )
  15. const enterLabelPrefix = "Enter "
  16. type setupOptions struct {
  17. name string
  18. context contextStore.AwsContext
  19. accessKeyID string
  20. secretAccessKey string
  21. }
  22. func (s setupOptions) unsetRequiredArgs() []string {
  23. unset := []string{}
  24. if s.context.Profile == "" {
  25. unset = append(unset, "profile")
  26. }
  27. if s.context.Region == "" {
  28. unset = append(unset, "region")
  29. }
  30. return unset
  31. }
  32. func SetupCommand() *cobra.Command {
  33. var opts setupOptions
  34. cmd := &cobra.Command{
  35. Use: "setup",
  36. Short: "",
  37. RunE: func(cmd *cobra.Command, args []string) error {
  38. if requiredFlag := opts.unsetRequiredArgs(); len(requiredFlag) > 0 {
  39. if err := interactiveCli(&opts); err != nil {
  40. return err
  41. }
  42. }
  43. if opts.accessKeyID != "" && opts.secretAccessKey != "" {
  44. if err := saveCredentials(opts.context.Profile, opts.accessKeyID, opts.secretAccessKey); err != nil {
  45. return err
  46. }
  47. }
  48. return contextStore.NewContext(opts.name, &opts.context)
  49. },
  50. }
  51. cmd.Flags().StringVarP(&opts.name, "name", "n", "aws", "Context Name")
  52. cmd.Flags().StringVarP(&opts.context.Profile, "profile", "p", "", "AWS Profile")
  53. cmd.Flags().StringVarP(&opts.context.Cluster, "cluster", "c", "", "ECS cluster")
  54. cmd.Flags().StringVarP(&opts.context.Region, "region", "r", "", "AWS region")
  55. cmd.Flags().StringVarP(&opts.accessKeyID, "aws-key-id", "k", "", "AWS Access Key ID")
  56. cmd.Flags().StringVarP(&opts.secretAccessKey, "aws-secret-key", "s", "", "AWS Secret Access Key")
  57. return cmd
  58. }
  59. func interactiveCli(opts *setupOptions) error {
  60. var section ini.Section
  61. if err := setContextName(opts); err != nil {
  62. return err
  63. }
  64. section, err := setProfile(opts, section)
  65. if err != nil {
  66. return err
  67. }
  68. if err := setCluster(opts, err); err != nil {
  69. return err
  70. }
  71. if err := setRegion(opts, section); err != nil {
  72. return err
  73. }
  74. if err := setCredentials(opts); err != nil {
  75. return err
  76. }
  77. return nil
  78. }
  79. func saveCredentials(profile string, accessKeyID string, secretAccessKey string) error {
  80. p := credentials.SharedCredentialsProvider{Profile: profile}
  81. _, err := p.Retrieve()
  82. if err == nil {
  83. fmt.Println("credentials already exists!")
  84. return nil
  85. }
  86. if err.(awserr.Error).Code() == "SharedCredsLoad" && err.(awserr.Error).Message() == "failed to load shared credentials file" {
  87. os.Create(p.Filename)
  88. }
  89. credIni, err := ini.Load(p.Filename)
  90. if err != nil {
  91. return err
  92. }
  93. section, err := credIni.NewSection(profile)
  94. if err != nil {
  95. return err
  96. }
  97. section.NewKey("aws_access_key_id", accessKeyID)
  98. section.NewKey("aws_secret_access_key", secretAccessKey)
  99. return credIni.SaveTo(p.Filename)
  100. }
  101. func awsProfiles(filename string) (map[string]ini.Section, error) {
  102. profiles := map[string]ini.Section{"new profile": {}}
  103. if filename == "" {
  104. filename = defaults.SharedConfigFilename()
  105. }
  106. credIni, err := ini.Load(filename)
  107. if err != nil {
  108. return nil, err
  109. }
  110. if err != nil {
  111. return nil, err
  112. }
  113. for _, section := range credIni.Sections() {
  114. if strings.HasPrefix(section.Name(), "profile") {
  115. profiles[section.Name()[len("profile "):]] = *section
  116. }
  117. }
  118. return profiles, nil
  119. }
  120. func setContextName(opts *setupOptions) error {
  121. if opts.name == "aws" {
  122. result, err := promptString(opts.name, "context name", enterLabelPrefix, 2)
  123. if err != nil {
  124. return err
  125. }
  126. opts.name = result
  127. }
  128. return nil
  129. }
  130. func setProfile(opts *setupOptions, section ini.Section) (ini.Section, error) {
  131. profilesList, err := awsProfiles("")
  132. if err != nil {
  133. return ini.Section{}, err
  134. }
  135. section, ok := profilesList[opts.context.Profile]
  136. if !ok {
  137. prompt := promptui.Select{
  138. Label: "Select AWS Profile",
  139. Items: reflect.ValueOf(profilesList).MapKeys(),
  140. }
  141. _, result, err := prompt.Run()
  142. if result == "new profile" {
  143. result, err := promptString(opts.context.Profile, "profile name", enterLabelPrefix, 2)
  144. if err != nil {
  145. return ini.Section{}, err
  146. }
  147. opts.context.Profile = result
  148. } else {
  149. section = profilesList[result]
  150. opts.context.Profile = result
  151. }
  152. if err != nil {
  153. return ini.Section{}, err
  154. }
  155. }
  156. return section, nil
  157. }
  158. func setRegion(opts *setupOptions, section ini.Section) error {
  159. defaultRegion := opts.context.Region
  160. if defaultRegion == "" && section.Name() != "" {
  161. region, err := section.GetKey("region")
  162. if err == nil {
  163. defaultRegion = region.Value()
  164. }
  165. }
  166. result, err := promptString(defaultRegion, "region", enterLabelPrefix, 2)
  167. if err != nil {
  168. return err
  169. }
  170. opts.context.Region = result
  171. return nil
  172. }
  173. func setCluster(opts *setupOptions, err error) error {
  174. result, err := promptString(opts.context.Cluster, "cluster name", enterLabelPrefix, 0)
  175. if err != nil {
  176. return err
  177. }
  178. opts.context.Cluster = result
  179. return nil
  180. }
  181. func setCredentials(opts *setupOptions) error {
  182. prompt := promptui.Prompt{
  183. Label: "Enter credentials",
  184. IsConfirm: true,
  185. }
  186. _, err := prompt.Run()
  187. if err == nil {
  188. result, err := promptString(opts.accessKeyID, "AWS Access Key ID", enterLabelPrefix, 3)
  189. if err != nil {
  190. return err
  191. }
  192. opts.accessKeyID = result
  193. prompt = promptui.Prompt{
  194. Label: "Enter AWS Secret Access Key",
  195. Validate: validateMinLen("AWS Secret Access Key", 3),
  196. Mask: '*',
  197. Default: opts.secretAccessKey,
  198. }
  199. result, err = prompt.Run()
  200. if err != nil {
  201. return err
  202. }
  203. opts.secretAccessKey = result
  204. }
  205. return nil
  206. }
  207. func promptString(defaultValue string, label string, labelPrefix string, minLength int) (string, error) {
  208. prompt := promptui.Prompt{
  209. Label: labelPrefix + label,
  210. Validate: validateMinLen(label, minLength),
  211. Default: defaultValue,
  212. }
  213. result, err := prompt.Run()
  214. if err != nil {
  215. return "", err
  216. }
  217. return result, nil
  218. }
  219. func validateMinLen(label string, minLength int) func(input string) error {
  220. return func(input string) error {
  221. if len(input) < minLength {
  222. return fmt.Errorf("%s must have more than %d characters", label, minLength)
  223. }
  224. return nil
  225. }
  226. }