backend.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  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/types"
  24. "github.com/pkg/errors"
  25. "github.com/sirupsen/logrus"
  26. "github.com/docker/api/azure/convert"
  27. "github.com/docker/api/azure/login"
  28. "github.com/docker/api/backend"
  29. "github.com/docker/api/compose"
  30. "github.com/docker/api/containers"
  31. apicontext "github.com/docker/api/context"
  32. "github.com/docker/api/context/cloud"
  33. "github.com/docker/api/context/store"
  34. "github.com/docker/api/errdefs"
  35. )
  36. const singleContainerName = "single--container--aci"
  37. // ErrNoSuchContainer is returned when the mentioned container does not exist
  38. var ErrNoSuchContainer = errors.New("no such container")
  39. func init() {
  40. backend.Register("aci", "aci", service, getCloudService)
  41. }
  42. func service(ctx context.Context) (backend.Service, error) {
  43. contextStore := store.ContextStore(ctx)
  44. currentContext := apicontext.CurrentContext(ctx)
  45. var aciContext store.AciContext
  46. if err := contextStore.GetEndpoint(currentContext, &aciContext); err != nil {
  47. return nil, err
  48. }
  49. return getAciAPIService(aciContext), nil
  50. }
  51. func getCloudService() (cloud.Service, error) {
  52. service, err := login.NewAzureLoginService()
  53. if err != nil {
  54. return nil, err
  55. }
  56. return &aciCloudService{
  57. loginService: service,
  58. }, nil
  59. }
  60. func getAciAPIService(aciCtx store.AciContext) *aciAPIService {
  61. return &aciAPIService{
  62. aciContainerService: &aciContainerService{
  63. ctx: aciCtx,
  64. },
  65. aciComposeService: &aciComposeService{
  66. ctx: aciCtx,
  67. },
  68. }
  69. }
  70. type aciAPIService struct {
  71. *aciContainerService
  72. *aciComposeService
  73. }
  74. func (a *aciAPIService) ContainerService() containers.Service {
  75. return a.aciContainerService
  76. }
  77. func (a *aciAPIService) ComposeService() compose.Service {
  78. return a.aciComposeService
  79. }
  80. type aciContainerService struct {
  81. ctx store.AciContext
  82. }
  83. func (cs *aciContainerService) List(ctx context.Context, _ bool) ([]containers.Container, error) {
  84. groupsClient, err := getContainerGroupsClient(cs.ctx.SubscriptionID)
  85. if err != nil {
  86. return nil, err
  87. }
  88. var containerGroups []containerinstance.ContainerGroup
  89. result, err := groupsClient.ListByResourceGroup(ctx, cs.ctx.ResourceGroup)
  90. if err != nil {
  91. return []containers.Container{}, err
  92. }
  93. for result.NotDone() {
  94. containerGroups = append(containerGroups, result.Values()...)
  95. if err := result.NextWithContext(ctx); err != nil {
  96. return []containers.Container{}, err
  97. }
  98. }
  99. var res []containers.Container
  100. for _, containerGroup := range containerGroups {
  101. group, err := groupsClient.Get(ctx, cs.ctx.ResourceGroup, *containerGroup.Name)
  102. if err != nil {
  103. return []containers.Container{}, err
  104. }
  105. for _, container := range *group.Containers {
  106. var containerID string
  107. // don't list sidecar container
  108. if *container.Name == convert.ComposeDNSSidecarName {
  109. continue
  110. }
  111. if *container.Name == singleContainerName {
  112. containerID = *containerGroup.Name
  113. } else {
  114. containerID = *containerGroup.Name + "_" + *container.Name
  115. }
  116. status := "Unknown"
  117. if container.InstanceView != nil && container.InstanceView.CurrentState != nil {
  118. status = *container.InstanceView.CurrentState.State
  119. }
  120. res = append(res, containers.Container{
  121. ID: containerID,
  122. Image: *container.Image,
  123. Status: status,
  124. Ports: convert.ToPorts(group.IPAddress, *container.Ports),
  125. })
  126. }
  127. }
  128. return res, nil
  129. }
  130. func (cs *aciContainerService) Run(ctx context.Context, r containers.ContainerConfig) error {
  131. var ports []types.ServicePortConfig
  132. for _, p := range r.Ports {
  133. ports = append(ports, types.ServicePortConfig{
  134. Target: p.ContainerPort,
  135. Published: p.HostPort,
  136. })
  137. }
  138. projectVolumes, serviceConfigVolumes, err := convert.GetRunVolumes(r.Volumes)
  139. if err != nil {
  140. return err
  141. }
  142. project := compose.Project{
  143. Name: r.ID,
  144. Config: types.Config{
  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. }
  165. logrus.Debugf("Running container %q with name %q\n", r.Image, r.ID)
  166. groupDefinition, err := convert.ToContainerGroup(cs.ctx, project)
  167. if err != nil {
  168. return err
  169. }
  170. return createACIContainers(ctx, cs.ctx, groupDefinition)
  171. }
  172. func (cs *aciContainerService) Stop(ctx context.Context, containerName string, timeout *uint32) error {
  173. return errdefs.ErrNotImplemented
  174. }
  175. func getGroupAndContainerName(containerID string) (groupName string, containerName string) {
  176. tokens := strings.Split(containerID, "_")
  177. groupName = tokens[0]
  178. if len(tokens) > 1 {
  179. containerName = tokens[len(tokens)-1]
  180. groupName = containerID[:len(containerID)-(len(containerName)+1)]
  181. } else {
  182. containerName = singleContainerName
  183. }
  184. return groupName, containerName
  185. }
  186. func (cs *aciContainerService) Exec(ctx context.Context, name string, command string, reader io.Reader, writer io.Writer) error {
  187. groupName, containerAciName := getGroupAndContainerName(name)
  188. containerExecResponse, err := execACIContainer(ctx, cs.ctx, command, groupName, containerAciName)
  189. if err != nil {
  190. return err
  191. }
  192. return exec(
  193. context.Background(),
  194. *containerExecResponse.WebSocketURI,
  195. *containerExecResponse.Password,
  196. reader,
  197. writer,
  198. )
  199. }
  200. func (cs *aciContainerService) Logs(ctx context.Context, containerName string, req containers.LogsRequest) error {
  201. groupName, containerAciName := getGroupAndContainerName(containerName)
  202. var tail *int32
  203. if req.Follow {
  204. return streamLogs(ctx, cs.ctx, groupName, containerAciName, req.Writer)
  205. }
  206. if req.Tail != "all" {
  207. reqTail, err := strconv.Atoi(req.Tail)
  208. if err != nil {
  209. return err
  210. }
  211. i32 := int32(reqTail)
  212. tail = &i32
  213. }
  214. logs, err := getACIContainerLogs(ctx, cs.ctx, groupName, containerAciName, tail)
  215. if err != nil {
  216. return err
  217. }
  218. _, err = fmt.Fprint(req.Writer, logs)
  219. return err
  220. }
  221. func (cs *aciContainerService) Delete(ctx context.Context, containerID string, _ bool) error {
  222. cg, err := deleteACIContainerGroup(ctx, cs.ctx, containerID)
  223. if err != nil {
  224. return err
  225. }
  226. if cg.StatusCode == http.StatusNoContent {
  227. return ErrNoSuchContainer
  228. }
  229. return err
  230. }
  231. func (cs *aciContainerService) Inspect(ctx context.Context, containerID string) (containers.Container, error) {
  232. groupName, containerName := getGroupAndContainerName(containerID)
  233. cg, err := getACIContainerGroup(ctx, cs.ctx, groupName)
  234. if err != nil {
  235. return containers.Container{}, err
  236. }
  237. if cg.StatusCode == http.StatusNoContent {
  238. return containers.Container{}, ErrNoSuchContainer
  239. }
  240. var cc containerinstance.Container
  241. var found = false
  242. for _, c := range *cg.Containers {
  243. if to.String(c.Name) == containerName {
  244. cc = c
  245. found = true
  246. break
  247. }
  248. }
  249. if !found {
  250. return containers.Container{}, ErrNoSuchContainer
  251. }
  252. return convert.ContainerGroupToContainer(containerID, cg, cc)
  253. }
  254. type aciComposeService struct {
  255. ctx store.AciContext
  256. }
  257. func (cs *aciComposeService) Up(ctx context.Context, opts compose.ProjectOptions) error {
  258. project, err := compose.ProjectFromOptions(&opts)
  259. if err != nil {
  260. return err
  261. }
  262. logrus.Debugf("Up on project with name %q\n", project.Name)
  263. groupDefinition, err := convert.ToContainerGroup(cs.ctx, *project)
  264. if err != nil {
  265. return err
  266. }
  267. return createOrUpdateACIContainers(ctx, cs.ctx, groupDefinition)
  268. }
  269. func (cs *aciComposeService) Down(ctx context.Context, opts compose.ProjectOptions) error {
  270. project, err := compose.ProjectFromOptions(&opts)
  271. if err != nil {
  272. return err
  273. }
  274. logrus.Debugf("Down on project with name %q\n", project.Name)
  275. cg, err := deleteACIContainerGroup(ctx, cs.ctx, project.Name)
  276. if err != nil {
  277. return err
  278. }
  279. if cg.StatusCode == http.StatusNoContent {
  280. return ErrNoSuchContainer
  281. }
  282. return err
  283. }
  284. type aciCloudService struct {
  285. loginService login.AzureLoginService
  286. }
  287. func (cs *aciCloudService) Login(ctx context.Context, params map[string]string) error {
  288. return cs.loginService.Login(ctx, params[login.TenantIDLoginParam])
  289. }
  290. func (cs *aciCloudService) CreateContextData(ctx context.Context, params map[string]string) (interface{}, string, error) {
  291. contextHelper := newContextCreateHelper()
  292. return contextHelper.createContextData(ctx, params)
  293. }