Skip to content

Commit 0eac03c

Browse files
alacukupoiana
authored andcommitted
feat(artifact/follow): add new command "artifact follow"
The new command accepts multiple artifact references. For each reference it starts a new go routine which tracks the artifact and monitors it for changes such new versions. In case of new artifact version it downloads and installs it in the local filesystem. The sync period can be customized using the "--every, -e" flag. The default values is set to 24h. Keep in mind that the command does not exit, but runs in daemon mode until it is stopped by a termination signal. Signed-off-by: Aldo Lacuku <[email protected]>
1 parent 084ac34 commit 0eac03c

File tree

4 files changed

+196
-0
lines changed

4 files changed

+196
-0
lines changed

internal/artifact/follow/doc.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2022 The Falco Authors
2+
//
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+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package follow defines the business logic to follow artifacts. Periodically checks if there are updates
16+
// and downlods them if any.
17+
package follow

internal/artifact/follow/follow.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Copyright 2022 The Falco Authors
2+
//
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+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package follow
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"sync"
21+
"time"
22+
23+
"github.com/spf13/cobra"
24+
25+
"github.com/falcosecurity/falcoctl/internal/config"
26+
"github.com/falcosecurity/falcoctl/internal/follower"
27+
"github.com/falcosecurity/falcoctl/internal/utils"
28+
"github.com/falcosecurity/falcoctl/pkg/index"
29+
"github.com/falcosecurity/falcoctl/pkg/options"
30+
)
31+
32+
const timeout = time.Second * 5
33+
34+
var longFollow = `Follow a list of artifacts from remote registry. It periodically
35+
checks if the artifacts changed and downloads the latest version based on the configured
36+
tags.
37+
38+
Artifacts are passed as arguments. By just providing the name of the artifact the
39+
command will search for the artifact in the configured index files (see index command).
40+
If found it will use the 'registry' and 'repository' as specifiend in the index file.
41+
42+
Example - Install and follow "cloudtrail" plugin using the "latest" (default) tag using the info found in the index file:
43+
falcoctl artifact follow cloudtrail
44+
45+
Example - Install and follow "cloudtrail:0.6.0" plugin using the "0.6.0" tag. Here we explicitly set the tag:
46+
falcoctl artifact follow cloudtrail:0.6.0
47+
48+
Example - Install and follow "cloudtrail" plugin and "cloutrail-rules" using the "latest" (default) tag:
49+
falcoctl artifact follow cloudtrail cloudtrail-rules
50+
51+
52+
The command also supports the references for the artifacts composed by "registry" + "repository" + "tag":
53+
54+
Example - Install and follow "cloudtrail" plugins using the full artifact reference:
55+
falcoctl artifact follow ghcr.io/falcosecurity/plugins/ruleset/cloudtrail:0.6.0-rc1
56+
`
57+
58+
type artifactFollowOptions struct {
59+
*options.CommonOptions
60+
rulesfilesDir string
61+
pluginsDir string
62+
every time.Duration
63+
closeChan chan bool
64+
}
65+
66+
// NewArtifactFollowCmd returns the artifact follow command.
67+
func NewArtifactFollowCmd(ctx context.Context, opt *options.CommonOptions) *cobra.Command {
68+
o := artifactFollowOptions{
69+
CommonOptions: opt,
70+
closeChan: make(chan bool),
71+
}
72+
73+
cmd := &cobra.Command{
74+
Use: "follow [ref1 [ref2 ...]] [flags]",
75+
Short: "Install a list of artifacts and continuously checks if there are updates",
76+
Long: longFollow,
77+
Args: cobra.MinimumNArgs(1),
78+
Run: func(cmd *cobra.Command, args []string) {
79+
o.Printer.CheckErr(o.RunArtifactFollow(ctx, args))
80+
},
81+
}
82+
83+
cmd.Flags().DurationVarP(&o.every, "every", "e", config.FollowResync, "Time interval how often it checks for a new version of the "+
84+
"artifact")
85+
// TODO (alacuku): move it in a dedicate data structure since they are in common with artifactInstall command.
86+
cmd.Flags().StringVarP(&o.rulesfilesDir, "rulesfiles-dir", "", config.RulesfilesDir,
87+
"Directory where to install rules")
88+
cmd.Flags().StringVarP(&o.pluginsDir, "plugins-dir", "", config.PluginsDir,
89+
"Directory where to install plugins")
90+
91+
return cmd
92+
}
93+
94+
// RunArtifactFollow executes the business logic for the artifact follow command.
95+
func (o *artifactFollowOptions) RunArtifactFollow(ctx context.Context, args []string) error {
96+
o.Printer.Info.Printfln("Reading all configured index files from %q", config.IndexesFile)
97+
indexConfig, err := index.NewConfig(config.IndexesFile)
98+
if err != nil {
99+
return err
100+
}
101+
102+
mergedIndexes, err := utils.Indexes(indexConfig, config.FalcoctlPath)
103+
if err != nil {
104+
return err
105+
}
106+
107+
if len(mergedIndexes.Entries) < 1 {
108+
o.Printer.Warning.Println("No configured index. Consider to configure one using the 'index add' command.")
109+
}
110+
111+
var wg sync.WaitGroup
112+
// Disable styling
113+
o.Printer.DisableStylingf()
114+
// For each artifact create a follower.
115+
var followers = make(map[string]*follower.Follower, 0)
116+
for _, a := range args {
117+
o.Printer.Info.Printfln("Creating follower for %q", a)
118+
ref, err := utils.ParseReference(mergedIndexes, a)
119+
if err != nil {
120+
return fmt.Errorf("unable to parse artifact reference for %q: %w", a, err)
121+
}
122+
123+
cfg := &follower.Config{
124+
WaitGroup: &wg,
125+
Resync: o.every,
126+
RulefilesDir: o.rulesfilesDir,
127+
PluginsDir: o.pluginsDir,
128+
ArtifactReference: ref,
129+
Verbose: o.IsVerbose(),
130+
CloseChan: o.closeChan,
131+
}
132+
fol, err := follower.New(ctx, ref, o.Printer, cfg)
133+
if err != nil {
134+
return fmt.Errorf("unable to create the follower for ref %q: %w", ref, err)
135+
}
136+
wg.Add(1)
137+
followers[ref] = fol
138+
}
139+
// Enable styling
140+
o.Printer.EnableStyling()
141+
142+
for k, f := range followers {
143+
o.Printer.Info.Printfln("Starting follower for %q", k)
144+
go f.Follow(ctx)
145+
}
146+
147+
// Wait until we receive a signal to be terminated
148+
<-ctx.Done()
149+
150+
// We are done, shutdown the followers.
151+
o.Printer.DefaultText.Printfln("closing followers...")
152+
close(o.closeChan)
153+
154+
// Wait for the followers to shutdown or that the timer expires.
155+
doneChan := make(chan bool)
156+
157+
go func() {
158+
wg.Wait()
159+
close(doneChan)
160+
}()
161+
162+
select {
163+
case <-doneChan:
164+
o.Printer.DefaultText.Printfln("followers correctly stopped.")
165+
case <-time.After(timeout):
166+
o.Printer.DefaultText.Printfln("Timed out waiting for followers to exit")
167+
}
168+
169+
return nil
170+
}

internal/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package config
1616

1717
import (
1818
"path/filepath"
19+
"time"
1920

2021
"github.com/docker/docker/pkg/homedir"
2122
)
@@ -34,6 +35,9 @@ const (
3435
PluginsDir = "/usr/share/falco/plugins"
3536
// RulesfilesDir default path where rulesfiles are installed.
3637
RulesfilesDir = "/etc/falco"
38+
// FollowResync time interval how often it checks for newer version of the artifact.
39+
// Default values is set every 24 hours.
40+
FollowResync = time.Hour * 24
3741
)
3842

3943
func init() {

pkg/options/common_options.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ func (o *CommonOptions) Initialize(cfgs ...Configs) {
7272
o.Printer = output.NewPrinter(o.printerScope, o.disableStyling, o.verbose, o.writer)
7373
}
7474

75+
// IsVerbose used to check if the verbose flag is set or not.
76+
func (o *CommonOptions) IsVerbose() bool {
77+
return o.verbose
78+
}
79+
7580
// AddFlags registers the common flags.
7681
func (o *CommonOptions) AddFlags(flags *pflag.FlagSet) {
7782
flags.BoolVarP(&o.verbose, "verbose", "v", false, "Enable verbose logs (default false)")

0 commit comments

Comments
 (0)