1/*
2   Copyright The containerd Authors.
3
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
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package leases
18
19import (
20	"fmt"
21	"os"
22	"sort"
23	"strings"
24	"text/tabwriter"
25	"time"
26
27	"github.com/containerd/containerd/cmd/ctr/commands"
28	"github.com/containerd/containerd/leases"
29	"github.com/pkg/errors"
30	"github.com/urfave/cli"
31)
32
33// Command is the cli command for managing content
34var Command = cli.Command{
35	Name:  "leases",
36	Usage: "manage leases",
37	Subcommands: cli.Commands{
38		listCommand,
39		createCommand,
40		deleteCommand,
41	},
42}
43
44var listCommand = cli.Command{
45
46	Name:        "list",
47	Aliases:     []string{"ls"},
48	Usage:       "list all active leases",
49	ArgsUsage:   "[flags] <filter>",
50	Description: "list active leases by containerd",
51	Flags: []cli.Flag{
52		cli.BoolFlag{
53			Name:  "quiet, q",
54			Usage: "print only the blob digest",
55		},
56	},
57	Action: func(context *cli.Context) error {
58		var (
59			filters = context.Args()
60			quiet   = context.Bool("quiet")
61		)
62		client, ctx, cancel, err := commands.NewClient(context)
63		if err != nil {
64			return err
65		}
66		defer cancel()
67
68		ls := client.LeasesService()
69
70		leaseList, err := ls.List(ctx, filters...)
71		if err != nil {
72			return errors.Wrap(err, "failed to list leases")
73		}
74		if quiet {
75			for _, l := range leaseList {
76				fmt.Println(l.ID)
77			}
78			return nil
79		}
80		tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
81		fmt.Fprintln(tw, "ID\tCREATED AT\tLABELS\t")
82		for _, l := range leaseList {
83			labels := "-"
84			if len(l.Labels) > 0 {
85				var pairs []string
86				for k, v := range l.Labels {
87					pairs = append(pairs, fmt.Sprintf("%v=%v", k, v))
88				}
89				sort.Strings(pairs)
90				labels = strings.Join(pairs, ",")
91			}
92
93			fmt.Fprintf(tw, "%v\t%v\t%s\t\n",
94				l.ID,
95				l.CreatedAt.Local().Format(time.RFC3339),
96				labels)
97		}
98
99		return tw.Flush()
100	},
101}
102
103var createCommand = cli.Command{
104	Name:        "create",
105	Usage:       "create lease",
106	ArgsUsage:   "[flags] <label>=<value> ...",
107	Description: "create a new lease",
108	Flags: []cli.Flag{
109		cli.StringFlag{
110			Name:  "id",
111			Usage: "set the id for the lease, will be generated by default",
112		},
113		cli.DurationFlag{
114			Name:  "expires, x",
115			Usage: "expiration of lease (0 value will not expire)",
116			Value: 24 * time.Hour,
117		},
118	},
119	Action: func(context *cli.Context) error {
120		var labelstr = context.Args()
121		client, ctx, cancel, err := commands.NewClient(context)
122		if err != nil {
123			return err
124		}
125		defer cancel()
126
127		ls := client.LeasesService()
128		opts := []leases.Opt{}
129		if len(labelstr) > 0 {
130			labels := map[string]string{}
131			for _, lstr := range labelstr {
132				l := strings.SplitN(lstr, "=", 2)
133				if len(l) == 1 {
134					labels[l[0]] = ""
135				} else {
136					labels[l[0]] = l[1]
137				}
138			}
139			opts = append(opts, leases.WithLabels(labels))
140		}
141
142		if id := context.String("id"); id != "" {
143			opts = append(opts, leases.WithID(id))
144		}
145		if exp := context.Duration("expires"); exp > 0 {
146			opts = append(opts, leases.WithExpiration(exp))
147		}
148
149		l, err := ls.Create(ctx, opts...)
150		if err != nil {
151			return err
152		}
153
154		fmt.Println(l.ID)
155
156		return nil
157	},
158}
159
160var deleteCommand = cli.Command{
161	Name:        "delete",
162	Aliases:     []string{"rm"},
163	Usage:       "delete a lease",
164	ArgsUsage:   "[flags] <lease id> ...",
165	Description: "delete a lease",
166	Flags: []cli.Flag{
167		cli.BoolFlag{
168			Name:  "sync",
169			Usage: "Synchronously remove leases and all unreferenced resources",
170		},
171	},
172	Action: func(context *cli.Context) error {
173		var lids = context.Args()
174		if len(lids) == 0 {
175			return cli.ShowSubcommandHelp(context)
176		}
177		client, ctx, cancel, err := commands.NewClient(context)
178		if err != nil {
179			return err
180		}
181		defer cancel()
182
183		ls := client.LeasesService()
184		sync := context.Bool("sync")
185		for i, lid := range lids {
186			var opts []leases.DeleteOpt
187			if sync && i == len(lids)-1 {
188				opts = append(opts, leases.SynchronousDelete)
189			}
190
191			lease := leases.Lease{
192				ID: lid,
193			}
194			if err := ls.Delete(ctx, lease, opts...); err != nil {
195				return err
196			}
197			fmt.Println(lid)
198		}
199
200		return nil
201	},
202}
203