Skip to content

Commit fa3c305

Browse files
committed
environment variable support, regular expression, go-lint, update readme
1 parent 9f968b1 commit fa3c305

File tree

4 files changed

+172
-59
lines changed

4 files changed

+172
-59
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.idea/
22
.credentials
3-
.DS_Store
3+
.DS_Store
4+
nexus-cli

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,26 @@ $ nexus-cli image info -name mlabouardy/nginx -tag 1.2.0
7676
$ nexus-cli image delete -name mlabouardy/nginx -tag 1.2.0
7777
```
7878

79+
```
80+
$ nexus-cli image tags -name mlabouardy/nginx -v -e v1 -e latest
81+
```
82+
83+
```
84+
$ nexus-cli image tags -name mlabouardy/nginx -e 1.2
85+
```
86+
87+
```
88+
$ nexus-cli image delete -name mlabouardy/nginx -e '!feature'
89+
```
90+
7991
```
8092
$ nexus-cli image delete -name mlabouardy/nginx -keep 4
8193
```
8294

95+
## Caveats
96+
97+
Deletion of image tags is done using a tag name, but rather using a checksum of the image. If you push an image to a registry more than once (e.g. as `1.0.0` and also as `latest`). The deletion will still use the image checksum. Thus the deletion of a single tag is no problem. If the tag is not unique, the deletion will **delete a random tag** matching the checksum.
98+
8399
## Tutorials
84100

85101
* [Cleanup old Docker images from Nexus Repository](http://www.blog.labouardy.com/cleanup-old-docker-images-from-nexus-repository/)

main.go

+112-32
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ package main
33
import (
44
"fmt"
55
"html/template"
6+
"log"
67
"os"
8+
"regexp"
9+
"strings"
710

8-
"github.com/mlabouardy/nexus-cli/registry"
11+
"github.com/moepi/nexus-cli/registry"
912
"github.com/urfave/cli"
1013
)
1114

1215
const (
13-
CREDENTIALS_TEMPLATES = `# Nexus Credentials
16+
credentialsTemplates = `# Nexus Credentials
1417
nexus_host = "{{ .Host }}"
1518
nexus_username = "{{ .Username }}"
1619
nexus_password = "{{ .Password }}"
@@ -21,7 +24,7 @@ func main() {
2124
app := cli.NewApp()
2225
app.Name = "Nexus CLI"
2326
app.Usage = "Manage Docker Private Registry on Nexus"
24-
app.Version = "1.0.0-beta"
27+
app.Version = "1.0.0-beta-2"
2528
app.Authors = []cli.Author{
2629
cli.Author{
2730
Name: "Mohamed Labouardy",
@@ -55,6 +58,14 @@ func main() {
5558
Name: "name, n",
5659
Usage: "List tags by image name",
5760
},
61+
cli.StringSliceFlag{
62+
Name: "expression, e",
63+
Usage: "Filter tags by regular expression",
64+
},
65+
cli.BoolFlag{
66+
Name: "invert, v",
67+
Usage: "Invert filter results",
68+
},
5869
},
5970
Action: func(c *cli.Context) error {
6071
return listTagsByImage(c)
@@ -77,7 +88,7 @@ func main() {
7788
},
7889
{
7990
Name: "delete",
80-
Usage: "Delete an image",
91+
Usage: "Delete images",
8192
Flags: []cli.Flag{
8293
cli.StringFlag{
8394
Name: "name, n",
@@ -88,9 +99,17 @@ func main() {
8899
cli.StringFlag{
89100
Name: "keep, k",
90101
},
102+
cli.StringSliceFlag{
103+
Name: "expression, e",
104+
Usage: "Filter tags by regular expression",
105+
},
106+
cli.BoolFlag{
107+
Name: "invert, v",
108+
Usage: "Invert results filter expressions",
109+
},
91110
},
92111
Action: func(c *cli.Context) error {
93-
return deleteImage(c)
112+
return deleteImages(c)
94113
},
95114
},
96115
},
@@ -125,7 +144,7 @@ func setNexusCredentials(c *cli.Context) error {
125144
repository,
126145
}
127146

128-
tmpl, err := template.New(".credentials").Parse(CREDENTIALS_TEMPLATES)
147+
tmpl, err := template.New(".credentials").Parse(credentialsTemplates)
129148
if err != nil {
130149
return cli.NewExitError(err.Error(), 1)
131150
}
@@ -158,6 +177,36 @@ func listImages(c *cli.Context) error {
158177
return nil
159178
}
160179

180+
func filterTagsByRegex(tags []string, expressions []string, invert bool) ([]string, error) {
181+
var retTags []string
182+
if len(expressions) == 0 {
183+
return tags, nil
184+
}
185+
for _, tag := range tags {
186+
tagMiss := false
187+
for _, expression := range expressions {
188+
var expressionBool = !invert
189+
if strings.HasPrefix(expression, "!") {
190+
expressionBool = invert
191+
expression = strings.Trim(expression, "!")
192+
}
193+
retVal, err := regexp.MatchString(expression, tag)
194+
if err != nil {
195+
return retTags, err
196+
}
197+
if retVal != expressionBool {
198+
tagMiss = true
199+
break
200+
}
201+
}
202+
// tag must match all expression, so continue with next tag on match
203+
if !tagMiss {
204+
retTags = append(retTags, tag)
205+
}
206+
}
207+
return retTags, nil
208+
}
209+
161210
func listTagsByImage(c *cli.Context) error {
162211
var imgName = c.String("name")
163212
r, err := registry.NewRegistry()
@@ -169,6 +218,12 @@ func listTagsByImage(c *cli.Context) error {
169218
}
170219
tags, err := r.ListTagsByImage(imgName)
171220

221+
// filter tags by expressions
222+
tags, err = filterTagsByRegex(tags, c.StringSlice("expression"), c.Bool("invert"))
223+
if err != nil {
224+
log.Fatal(err)
225+
}
226+
172227
compareStringNumber := func(str1, str2 string) bool {
173228
return extractNumberFromString(str1) < extractNumberFromString(str2)
174229
}
@@ -207,46 +262,71 @@ func showImageInfo(c *cli.Context) error {
207262
return nil
208263
}
209264

210-
func deleteImage(c *cli.Context) error {
265+
func deleteImages(c *cli.Context) error {
211266
var imgName = c.String("name")
212267
var tag = c.String("tag")
213268
var keep = c.Int("keep")
269+
var invert = c.Bool("invert")
270+
271+
// Show help if no image name is present
214272
if imgName == "" {
215273
fmt.Fprintf(c.App.Writer, "You should specify the image name\n")
216274
cli.ShowSubcommandHelp(c)
217-
} else {
218-
r, err := registry.NewRegistry()
275+
return nil
276+
}
277+
278+
r, err := registry.NewRegistry()
279+
if err != nil {
280+
return cli.NewExitError(err.Error(), 1)
281+
}
282+
283+
// if a specific tag is provided, ignore all other options
284+
if tag != "" {
285+
err = r.DeleteImageByTag(imgName, tag)
219286
if err != nil {
220287
return cli.NewExitError(err.Error(), 1)
221288
}
222-
if tag == "" {
223-
if keep == 0 {
224-
fmt.Fprintf(c.App.Writer, "You should either specify the tag or how many images you want to keep\n")
225-
cli.ShowSubcommandHelp(c)
226-
} else {
227-
tags, err := r.ListTagsByImage(imgName)
228-
compareStringNumber := func(str1, str2 string) bool {
229-
return extractNumberFromString(str1) < extractNumberFromString(str2)
230-
}
231-
Compare(compareStringNumber).Sort(tags)
232-
if err != nil {
233-
return cli.NewExitError(err.Error(), 1)
234-
}
235-
if len(tags) >= keep {
236-
for _, tag := range tags[:len(tags)-keep] {
237-
fmt.Printf("%s:%s image will be deleted ...\n", imgName, tag)
238-
r.DeleteImageByTag(imgName, tag)
239-
}
240-
} else {
241-
fmt.Printf("Only %d images are available\n", len(tags))
242-
}
243-
}
244-
} else {
289+
return nil
290+
}
291+
292+
// Get list of tags and filter them by all expressions provided
293+
tags, err := r.ListTagsByImage(imgName)
294+
tags, err = filterTagsByRegex(tags, c.StringSlice("expression"), invert)
295+
if err != nil {
296+
fmt.Fprintf(c.App.Writer, "Could not filter tags by regular expressions: %s\n", err)
297+
return err
298+
}
299+
300+
// if no keep is specified, all flags are unset. Show help and exit.
301+
if c.IsSet("keep") == false && len(c.StringSlice("expression")) == 0 {
302+
fmt.Fprintf(c.App.Writer, "You should either specify use tag / filter expressions, or specify how many images you want to keep\n")
303+
cli.ShowSubcommandHelp(c)
304+
return fmt.Errorf("You should either specify use tag / filter expressions, or specify how many images you want to keep")
305+
}
306+
307+
if len(tags) == 0 && !c.IsSet("keep") {
308+
fmt.Fprintf(c.App.Writer, "No images selected for deletion\n")
309+
return fmt.Errorf("No images selected for deletion")
310+
}
311+
312+
// Remove images by using keep flag
313+
compareStringNumber := func(str1, str2 string) bool {
314+
return extractNumberFromString(str1) < extractNumberFromString(str2)
315+
}
316+
Compare(compareStringNumber).Sort(tags)
317+
if err != nil {
318+
return cli.NewExitError(err.Error(), 1)
319+
}
320+
if len(tags) >= keep {
321+
for _, tag := range tags[:len(tags)-keep] {
322+
fmt.Printf("%s:%s image will be deleted ...\n", imgName, tag)
245323
err = r.DeleteImageByTag(imgName, tag)
246324
if err != nil {
247325
return cli.NewExitError(err.Error(), 1)
248326
}
249327
}
328+
} else {
329+
fmt.Printf("Only %d images are available\n", len(tags))
250330
}
251331
return nil
252332
}

0 commit comments

Comments
 (0)