backend.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /*
  2. Copyright 2020 Docker, Inc.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package azure
  14. import (
  15. "context"
  16. "fmt"
  17. "io"
  18. "net/http"
  19. "strconv"
  20. "strings"
  21. "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
  22. "github.com/Azure/go-autorest/autorest/to"
  23. "github.com/compose-spec/compose-go/cli"
  24. "github.com/compose-spec/compose-go/types"
  25. "github.com/pkg/errors"
  26. "github.com/sirupsen/logrus"
  27. "github.com/docker/api/azure/convert"
  28. "github.com/docker/api/azure/login"
  29. "github.com/docker/api/backend"
  30. "github.com/docker/api/compose"
  31. "github.com/docker/api/containers"
  32. apicontext "github.com/docker/api/context"
  33. "github.com/docker/api/context/cloud"
  34. "github.com/docker/api/context/store"
  35. "github.com/docker/api/errdefs"
  36. )
  37. const singleContainerName = "single--container--aci"
  38. // ErrNoSuchContainer is returned when the mentioned container does not exist
  39. var ErrNoSuchContainer = errors.New("no such container")
  40. func init() {
  41. backend.Register("aci", "aci", service, getCloudService)
  42. }
  43. func service(ctx context.Context) (backend.Service, error) {
  44. contextStore := store.ContextStore(ctx)
  45. currentContext := apicontext.CurrentContext(ctx)
  46. var aciContext store.AciContext
  47. if err := contextStore.GetEndpoint(currentContext, &aciContext); err != nil {
  48. return nil, err
  49. }
  50. return getAciAPIService(aciContext), nil
  51. }
  52. func getCloudService() (cloud.Service, error) {
  53. service, err := login.NewAzureLoginService()
  54. if err != nil {
  55. return nil, err
  56. }
  57. return &aciCloudService{
  58. loginService: service,
  59. }, nil
  60. }
  61. func getAciAPIService(aciCtx store.AciContext) *aciAPIService {
  62. return &aciAPIService{
  63. aciContainerService: &aciContainerService{
  64. ctx: aciCtx,
  65. },
  66. aciComposeService: &aciComposeService{
  67. ctx: aciCtx,
  68. },
  69. }
  70. }
  71. type aciAPIService struct {
  72. *aciContainerService
  73. *aciComposeService
  74. }
  75. func (a *aciAPIService) ContainerService() containers.Service {
  76. return a.aciContainerService
  77. }
  78. func (a *aciAPIService) ComposeService() compose.Service {
  79. return a.aciComposeService
  80. }
  81. type aciContainerService struct {
  82. ctx store.AciContext
  83. }
  84. func (cs *aciContainerService) List(ctx context.Context, _ bool) ([]containers.Container, error) {
  85. groupsClient, err := getContainerGroupsClient(cs.ctx.SubscriptionID)
  86. if err != nil {
  87. return nil, err
  88. }
  89. var containerGroups []containerinstance.ContainerGroup
  90. result, err := groupsClient.ListByResourceGroup(ctx, cs.ctx.ResourceGroup)
  91. if err != nil {
  92. return []containers.Container{}, err
  93. }
  94. for result.NotDone() {
  95. containerGroups = append(containerGroups, result.Values()...)
  96. if err := result.NextWithContext(ctx); err != nil {
  97. return []containers.Container{}, err
  98. }
  99. }
  100. var res []containers.Container
  101. for _, containerGroup := range containerGroups {
  102. group, err := groupsClient.Get(ctx, cs.ctx.ResourceGroup, *containerGroup.Name)
  103. if err != nil {
  104. return []containers.Container{}, err
  105. }
  106. for _, container := range *group.Containers {
  107. var containerID string
  108. // don't list sidecar container
  109. if *container.Name == convert.ComposeDNSSidecarName {
  110. continue
  111. }
  112. if *container.Name == singleContainerName {
  113. containerID = *containerGroup.Name
  114. } else {
  115. containerID = *containerGroup.Name + "_" + *container.Name
  116. }
  117. status := "Unknown"
  118. if container.InstanceView != nil && container.InstanceView.CurrentState != nil {
  119. status = *container.InstanceView.CurrentState.State
  120. }
  121. res = append(res, containers.Container{
  122. ID: containerID,
  123. Image: *container.Image,
  124. Status: status,
  125. Ports: convert.ToPorts(group.IPAddress, *container.Ports),
  126. })
  127. }
  128. }
  129. return res, nil
  130. }
  131. func (cs *aciContainerService) Run(ctx context.Context, r containers.ContainerConfig) error {
  132. var ports []types.ServicePortConfig
  133. for _, p := range r.Ports {
  134. ports = append(ports, types.ServicePortConfig{
  135. Target: p.ContainerPort,
  136. Published: p.HostPort,
  137. })
  138. }
  139. projectVolumes, serviceConfigVolumes, err := convert.GetRunVolumes(r.Volumes)
  140. if err != nil {
  141. return err
  142. }
  143. project := types.Project{
  144. Name: r.ID,
  145. Services: []types.ServiceConfig{
  146. {
  147. Name: singleContainerName,
  148. Image: r.Image,
  149. Ports: ports,
  150. Labels: r.Labels,
  151. Volumes: serviceConfigVolumes,
  152. Deploy: &types.DeployConfig{
  153. Resources: types.Resources{
  154. Limits: &types.Resource{
  155. NanoCPUs: fmt.Sprintf("%f", r.CPULimit),
  156. MemoryBytes: types.UnitBytes(r.MemLimit.Value()),
  157. },
  158. },
  159. },
  160. },
  161. },
  162. Volumes: projectVolumes,
  163. }
  164. logrus.Debugf("Running container %q with name %q\n", r.Image, r.ID)
  165. groupDefinition, err := convert.ToContainerGroup(cs.ctx, project)
  166. if err != nil {
  167. return err
  168. }
  169. return createACIContainers(ctx, cs.ctx, groupDefinition)
  170. }
  171. func (cs *aciContainerService) Stop(ctx context.Context, containerName string, timeout *uint32) error {
  172. return errdefs.ErrNotImplemented
  173. }
  174. func getGroupAndContainerName(containerID string) (groupName string, containerName string) {
  175. tokens := strings.Split(containerID, "_")
  176. groupName = tokens[0]
  177. if len(tokens) > 1 {
  178. containerName = tokens[len(tokens)-1]
  179. groupName = containerID[:len(containerID)-(len(containerName)+1)]
  180. } else {
  181. containerName = singleContainerName
  182. }
  183. return groupName, containerName
  184. }
  185. func (cs *aciContainerService) Exec(ctx context.Context, name string, command string, reader io.Reader, writer io.Writer) error {
  186. groupName, containerAciName := getGroupAndContainerName(name)
  187. containerExecResponse, err := execACIContainer(ctx, cs.ctx, command, groupName, containerAciName)
  188. if err != nil {
  189. return err
  190. }
  191. return exec(
  192. context.Background(),
  193. *containerExecResponse.WebSocketURI,
  194. *containerExecResponse.Password,
  195. reader,
  196. writer,
  197. )
  198. }
  199. func (cs *aciContainerService) Logs(ctx context.Context, containerName string, req containers.LogsRequest) error {
  200. groupName, containerAciName := getGroupAndContainerName(containerName)
  201. var tail *int32
  202. if req.Follow {
  203. return streamLogs(ctx, cs.ctx, groupName, containerAciName, req.Writer)
  204. }
  205. if req.Tail != "all" {
  206. reqTail, err := strconv.Atoi(req.Tail)
  207. if err != nil {
  208. return err
  209. }
  210. i32 := int32(reqTail)
  211. tail = &i32
  212. }
  213. logs, err := getACIContainerLogs(ctx, cs.ctx, groupName, containerAciName, tail)
  214. if err != nil {
  215. return err
  216. }
  217. _, err = fmt.Fprint(req.Writer, logs)
  218. return err
  219. }
  220. func (cs *aciContainerService) Delete(ctx context.Context, containerID string, _ bool) error {
  221. cg, err := deleteACIContainerGroup(ctx, cs.ctx, containerID)
  222. if err != nil {
  223. return err
  224. }
  225. if cg.StatusCode == http.StatusNoContent {
  226. return ErrNoSuchContainer
  227. }
  228. return err
  229. }
  230. func (cs *aciContainerService) Inspect(ctx context.Context, containerID string) (containers.Container, error) {
  231. groupName, containerName := getGroupAndContainerName(containerID)
  232. cg, err := getACIContainerGroup(ctx, cs.ctx, groupName)
  233. if err != nil {
  234. return containers.Container{}, err
  235. }
  236. if cg.StatusCode == http.StatusNoContent {
  237. return containers.Container{}, ErrNoSuchContainer
  238. }
  239. var cc containerinstance.Container
  240. var found = false
  241. for _, c := range *cg.Containers {
  242. if to.String(c.Name) == containerName {
  243. cc = c
  244. found = true
  245. break
  246. }
  247. }
  248. if !found {
  249. return containers.Container{}, ErrNoSuchContainer
  250. }
  251. return convert.ContainerGroupToContainer(containerID, cg, cc)
  252. }
  253. type aciComposeService struct {
  254. ctx store.AciContext
  255. }
  256. func (cs *aciComposeService) Up(ctx context.Context, opts cli.ProjectOptions) error {
  257. project, err := cli.ProjectFromOptions(&opts)
  258. if err != nil {
  259. return err
  260. }
  261. logrus.Debugf("Up on project with name %q\n", project.Name)
  262. groupDefinition, err := convert.ToContainerGroup(cs.ctx, *project)
  263. if err != nil {
  264. return err
  265. }
  266. return createOrUpdateACIContainers(ctx, cs.ctx, groupDefinition)
  267. }
  268. func (cs *aciComposeService) Down(ctx context.Context, opts cli.ProjectOptions) error {
  269. project, err := cli.ProjectFromOptions(&opts)
  270. if err != nil {
  271. return err
  272. }
  273. logrus.Debugf("Down on project with name %q\n", project.Name)
  274. cg, err := deleteACIContainerGroup(ctx, cs.ctx, project.Name)
  275. if err != nil {
  276. return err
  277. }
  278. if cg.StatusCode == http.StatusNoContent {
  279. return ErrNoSuchContainer
  280. }
  281. return err
  282. }
  283. type aciCloudService struct {
  284. loginService login.AzureLoginService
  285. }
  286. func (cs *aciCloudService) Login(ctx context.Context, params map[string]string) error {
  287. return cs.loginService.Login(ctx)
  288. }
  289. func (cs *aciCloudService) CreateContextData(ctx context.Context, params map[string]string) (interface{}, string, error) {
  290. contextHelper := newContextCreateHelper()
  291. return contextHelper.createContextData(ctx, params)
  292. }