compose.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. // +build kube
  2. /*
  3. Copyright 2020 Docker Compose CLI authors
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package kube
  15. import (
  16. "context"
  17. "fmt"
  18. "strings"
  19. "github.com/compose-spec/compose-go/types"
  20. "github.com/pkg/errors"
  21. "github.com/docker/compose-cli/api/compose"
  22. apicontext "github.com/docker/compose-cli/api/context"
  23. "github.com/docker/compose-cli/api/context/store"
  24. "github.com/docker/compose-cli/api/errdefs"
  25. "github.com/docker/compose-cli/api/progress"
  26. "github.com/docker/compose-cli/kube/client"
  27. "github.com/docker/compose-cli/kube/helm"
  28. "github.com/docker/compose-cli/kube/resources"
  29. "github.com/docker/compose-cli/utils"
  30. )
  31. type composeService struct {
  32. sdk *helm.Actions
  33. client *client.KubeClient
  34. }
  35. // NewComposeService create a kubernetes implementation of the compose.Service API
  36. func NewComposeService() (compose.Service, error) {
  37. contextStore := store.Instance()
  38. currentContext := apicontext.Current()
  39. var kubeContext store.KubeContext
  40. if err := contextStore.GetEndpoint(currentContext, &kubeContext); err != nil {
  41. return nil, err
  42. }
  43. config, err := resources.LoadConfig(kubeContext)
  44. if err != nil {
  45. return nil, err
  46. }
  47. actions, err := helm.NewActions(config)
  48. if err != nil {
  49. return nil, err
  50. }
  51. apiClient, err := client.NewKubeClient(config)
  52. if err != nil {
  53. return nil, err
  54. }
  55. return &composeService{
  56. sdk: actions,
  57. client: apiClient,
  58. }, nil
  59. }
  60. // Up executes the equivalent to a `compose up`
  61. func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
  62. return progress.Run(ctx, func(ctx context.Context) error {
  63. return s.up(ctx, project)
  64. })
  65. }
  66. func (s *composeService) up(ctx context.Context, project *types.Project) error {
  67. w := progress.ContextWriter(ctx)
  68. eventName := "Convert Compose file to Helm charts"
  69. w.Event(progress.CreatingEvent(eventName))
  70. chart, err := helm.GetChartInMemory(project)
  71. if err != nil {
  72. return err
  73. }
  74. w.Event(progress.NewEvent(eventName, progress.Done, ""))
  75. stack, err := s.sdk.Get(project.Name)
  76. if err != nil || stack == nil {
  77. // install stack
  78. eventName = "Install Compose stack"
  79. w.Event(progress.CreatingEvent(eventName))
  80. err = s.sdk.InstallChart(project.Name, chart, func(format string, v ...interface{}) {
  81. message := fmt.Sprintf(format, v...)
  82. w.Event(progress.NewEvent(eventName, progress.Done, message))
  83. })
  84. } else {
  85. //update stack
  86. eventName = "Updating Compose stack"
  87. w.Event(progress.CreatingEvent(eventName))
  88. err = s.sdk.UpdateChart(project.Name, chart, func(format string, v ...interface{}) {
  89. message := fmt.Sprintf(format, v...)
  90. w.Event(progress.NewEvent(eventName, progress.Done, message))
  91. })
  92. }
  93. if err != nil {
  94. return err
  95. }
  96. w.Event(progress.NewEvent(eventName, progress.Done, ""))
  97. return s.client.WaitForPodState(ctx, client.WaitForStatusOptions{
  98. ProjectName: project.Name,
  99. Services: project.ServiceNames(),
  100. Status: compose.RUNNING,
  101. Log: func(pod string, stateReached bool, message string) {
  102. state := progress.Done
  103. if !stateReached {
  104. state = progress.Working
  105. }
  106. w.Event(progress.NewEvent(pod, state, message))
  107. },
  108. })
  109. }
  110. // Down executes the equivalent to a `compose down`
  111. func (s *composeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error {
  112. if options.Volumes {
  113. return errors.Wrap(errdefs.ErrNotImplemented, "--volumes option is not supported on Kubernetes")
  114. }
  115. if options.Images != "" {
  116. return errors.Wrap(errdefs.ErrNotImplemented, "--rmi option is not supported on Kubernetes")
  117. }
  118. return progress.Run(ctx, func(ctx context.Context) error {
  119. return s.down(ctx, projectName, options)
  120. })
  121. }
  122. func (s *composeService) down(ctx context.Context, projectName string, options compose.DownOptions) error {
  123. w := progress.ContextWriter(ctx)
  124. eventName := fmt.Sprintf("Remove %s", projectName)
  125. w.Event(progress.CreatingEvent(eventName))
  126. logger := func(format string, v ...interface{}) {
  127. message := fmt.Sprintf(format, v...)
  128. if strings.Contains(message, "Starting delete") {
  129. action := strings.Replace(message, "Starting delete for", "Delete", 1)
  130. w.Event(progress.CreatingEvent(action))
  131. w.Event(progress.NewEvent(action, progress.Done, ""))
  132. return
  133. }
  134. w.Event(progress.NewEvent(eventName, progress.Working, message))
  135. }
  136. err := s.sdk.Uninstall(projectName, logger)
  137. if err != nil {
  138. return err
  139. }
  140. events := []string{}
  141. err = s.client.WaitForPodState(ctx, client.WaitForStatusOptions{
  142. ProjectName: projectName,
  143. Services: nil,
  144. Status: compose.REMOVING,
  145. Timeout: options.Timeout,
  146. Log: func(pod string, stateReached bool, message string) {
  147. state := progress.Done
  148. if !stateReached {
  149. state = progress.Working
  150. }
  151. w.Event(progress.NewEvent(pod, state, message))
  152. if !utils.StringContains(events, pod) {
  153. events = append(events, pod)
  154. }
  155. },
  156. })
  157. if err != nil {
  158. return err
  159. }
  160. for _, e := range events {
  161. w.Event(progress.NewEvent(e, progress.Done, ""))
  162. }
  163. w.Event(progress.NewEvent(eventName, progress.Done, ""))
  164. return nil
  165. }
  166. // List executes the equivalent to a `docker stack ls`
  167. func (s *composeService) List(ctx context.Context, opts compose.ListOptions) ([]compose.Stack, error) {
  168. return s.sdk.ListReleases()
  169. }
  170. // Build executes the equivalent to a `compose build`
  171. func (s *composeService) Build(ctx context.Context, project *types.Project, options compose.BuildOptions) error {
  172. return errdefs.ErrNotImplemented
  173. }
  174. // Push executes the equivalent ot a `compose push`
  175. func (s *composeService) Push(ctx context.Context, project *types.Project, options compose.PushOptions) error {
  176. return errdefs.ErrNotImplemented
  177. }
  178. // Pull executes the equivalent of a `compose pull`
  179. func (s *composeService) Pull(ctx context.Context, project *types.Project, options compose.PullOptions) error {
  180. return errdefs.ErrNotImplemented
  181. }
  182. // Create executes the equivalent to a `compose create`
  183. func (s *composeService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
  184. return errdefs.ErrNotImplemented
  185. }
  186. // Start executes the equivalent to a `compose start`
  187. func (s *composeService) Start(ctx context.Context, project *types.Project, options compose.StartOptions) error {
  188. return errdefs.ErrNotImplemented
  189. }
  190. // Restart executes the equivalent to a `compose restart`
  191. func (s *composeService) Restart(ctx context.Context, project *types.Project, options compose.RestartOptions) error {
  192. return errdefs.ErrNotImplemented
  193. }
  194. // Stop executes the equivalent to a `compose stop`
  195. func (s *composeService) Stop(ctx context.Context, project *types.Project, options compose.StopOptions) error {
  196. return errdefs.ErrNotImplemented
  197. }
  198. // Copy copies a file/folder between a service container and the local filesystem
  199. func (s *composeService) Copy(ctx context.Context, project *types.Project, options compose.CopyOptions) error {
  200. return errdefs.ErrNotImplemented
  201. }
  202. // Logs executes the equivalent to a `compose logs`
  203. func (s *composeService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer, options compose.LogOptions) error {
  204. if len(options.Services) > 0 {
  205. consumer = utils.FilteredLogConsumer(consumer, options.Services)
  206. }
  207. return s.client.GetLogs(ctx, projectName, consumer, options.Follow)
  208. }
  209. // Ps executes the equivalent to a `compose ps`
  210. func (s *composeService) Ps(ctx context.Context, projectName string, options compose.PsOptions) ([]compose.ContainerSummary, error) {
  211. return s.client.GetContainers(ctx, projectName, options.All)
  212. }
  213. // Convert translate compose model into backend's native format
  214. func (s *composeService) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) {
  215. chart, err := helm.GetChartInMemory(project)
  216. if err != nil {
  217. return nil, err
  218. }
  219. if options.Output != "" {
  220. _, err := helm.SaveChart(chart, options.Output)
  221. return nil, err
  222. }
  223. buff := []byte{}
  224. for _, f := range chart.Raw {
  225. header := "\n" + f.Name + "\n" + strings.Repeat("-", len(f.Name)) + "\n"
  226. buff = append(buff, []byte(header)...)
  227. buff = append(buff, f.Data...)
  228. buff = append(buff, []byte("\n")...)
  229. }
  230. return buff, nil
  231. }
  232. func (s *composeService) Kill(ctx context.Context, project *types.Project, options compose.KillOptions) error {
  233. return errdefs.ErrNotImplemented
  234. }
  235. // RunOneOffContainer creates a service oneoff container and starts its dependencies
  236. func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
  237. return 0, errdefs.ErrNotImplemented
  238. }
  239. func (s *composeService) Remove(ctx context.Context, project *types.Project, options compose.RemoveOptions) error {
  240. return errdefs.ErrNotImplemented
  241. }
  242. // Exec executes a command in a running service container
  243. func (s *composeService) Exec(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
  244. return 0, s.client.Exec(ctx, project.Name, opts)
  245. }
  246. func (s *composeService) Pause(ctx context.Context, project string, options compose.PauseOptions) error {
  247. return errdefs.ErrNotImplemented
  248. }
  249. func (s *composeService) UnPause(ctx context.Context, project string, options compose.PauseOptions) error {
  250. return errdefs.ErrNotImplemented
  251. }
  252. func (s *composeService) Top(ctx context.Context, projectName string, services []string) ([]compose.ContainerProcSummary, error) {
  253. return nil, errdefs.ErrNotImplemented
  254. }
  255. func (s *composeService) Events(ctx context.Context, project string, options compose.EventsOptions) error {
  256. return errdefs.ErrNotImplemented
  257. }
  258. func (s *composeService) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
  259. return "", 0, errdefs.ErrNotImplemented
  260. }
  261. func (s *composeService) Images(ctx context.Context, projectName string, options compose.ImagesOptions) ([]compose.ImageSummary, error) {
  262. return nil, errdefs.ErrNotImplemented
  263. }