Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Add ignite configuration file support to ignited #655

Merged
merged 3 commits into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 3 additions & 66 deletions cmd/ignite/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@ import (
"github.com/weaveworks/ignite/cmd/ignite/cmd/imgcmd"
"github.com/weaveworks/ignite/cmd/ignite/cmd/kerncmd"
"github.com/weaveworks/ignite/cmd/ignite/cmd/vmcmd"
api "github.com/weaveworks/ignite/pkg/apis/ignite"
"github.com/weaveworks/ignite/pkg/apis/ignite/scheme"
"github.com/weaveworks/ignite/pkg/constants"
"github.com/weaveworks/ignite/pkg/config"
"github.com/weaveworks/ignite/pkg/logs"
logflag "github.com/weaveworks/ignite/pkg/logs/flag"
"github.com/weaveworks/ignite/pkg/network"
networkflag "github.com/weaveworks/ignite/pkg/network/flag"
"github.com/weaveworks/ignite/pkg/providers"
"github.com/weaveworks/ignite/pkg/providers/ignite"
"github.com/weaveworks/ignite/pkg/runtime"
runtimeflag "github.com/weaveworks/ignite/pkg/runtime/flag"
"github.com/weaveworks/ignite/pkg/util"
versioncmd "github.com/weaveworks/ignite/pkg/version/cmd"
Expand Down Expand Up @@ -59,48 +55,8 @@ func NewIgniteCommand(in io.Reader, out, err io.Writer) *cobra.Command {
// Create the directories needed for running
util.GenericCheckErr(util.CreateDirectories())

var configFilePath string

// If an ignite config flag is set, use it as the config file, else check
// if the global config file exists.
// If a config file path is passed, configure ignite using it.
if configPath != "" {
configFilePath = configPath
} else {
// Check the default config location.
if _, err := os.Stat(constants.IGNITE_CONFIG_FILE); !os.IsNotExist(err) {
log.Debugf("Found default ignite configuration file %s", constants.IGNITE_CONFIG_FILE)
configFilePath = constants.IGNITE_CONFIG_FILE
}
}

if configFilePath != "" {
log.Debugf("Using ignite configuration file %s", configFilePath)
var err error
providers.ComponentConfig, err = getConfigFromFile(configFilePath)
if err != nil {
log.Fatal(err)
}

// Set providers runtime and network plugin if found in config
// and not set explicitly via flags.
if providers.ComponentConfig.Spec.Runtime != "" && providers.RuntimeName == "" {
providers.RuntimeName = providers.ComponentConfig.Spec.Runtime
}
if providers.ComponentConfig.Spec.NetworkPlugin != "" && providers.NetworkPluginName == "" {
providers.NetworkPluginName = providers.ComponentConfig.Spec.NetworkPlugin
}
} else {
log.Debugln("Using ignite default configurations")
}

// Set the default runtime and network-plugin if it's not set by
// now.
if providers.RuntimeName == "" {
providers.RuntimeName = runtime.RuntimeContainerd
}
if providers.NetworkPluginName == "" {
providers.NetworkPluginName = network.PluginCNI
if err := config.ApplyConfiguration(configPath); err != nil {
log.Fatal(err)
}

// Populate the providers after flags have been parsed
Expand Down Expand Up @@ -173,22 +129,3 @@ func addGlobalFlags(fs *pflag.FlagSet) {
func AddQuietFlag(fs *pflag.FlagSet) {
fs.BoolVarP(&logs.Quiet, "quiet", "q", logs.Quiet, "The quiet mode allows for machine-parsable output by printing only IDs")
}

// getConfigFromFile reads a config file and returns ignite configuration.
func getConfigFromFile(configPath string) (*api.Configuration, error) {
componentConfig := &api.Configuration{}

// TODO: Fix libgitops DecodeFileInto to not allow empty files.
if err := scheme.Serializer.DecodeFileInto(configPath, componentConfig); err != nil {
return nil, err
}

// Ensure the read configuration is valid. If a file contains Kind and
// APIVersion, it's a valid config file. Empty file is invalid.
// NOTE: This is a workaround for libgitops allowing decode of empty file.
if componentConfig.Kind == "" || componentConfig.APIVersion == "" {
return nil, fmt.Errorf("invalid config file, Kind and APIVersion must be set")
}

return componentConfig, nil
}
13 changes: 11 additions & 2 deletions cmd/ignited/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import (
"os"

"github.com/lithammer/dedent"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/weaveworks/ignite/pkg/config"
"github.com/weaveworks/ignite/pkg/logs"
logflag "github.com/weaveworks/ignite/pkg/logs/flag"
networkflag "github.com/weaveworks/ignite/pkg/network/flag"
Expand All @@ -18,7 +19,10 @@ import (
versioncmd "github.com/weaveworks/ignite/pkg/version/cmd"
)

var logLevel = logrus.InfoLevel
var logLevel = log.InfoLevel

// Ignite config file path flag variable.
var configPath string

// NewIgnitedCommand returns the root command for ignited
func NewIgnitedCommand(in io.Reader, out, err io.Writer) *cobra.Command {
Expand All @@ -29,6 +33,10 @@ func NewIgnitedCommand(in io.Reader, out, err io.Writer) *cobra.Command {
// Set the desired logging level, now that the flags are parsed
logs.Logger.SetLevel(logLevel)

if err := config.ApplyConfiguration(configPath); err != nil {
log.Fatal(err)
}

// Populate the providers after flags have been parsed
if err := providers.Populate(ignite.Providers); err != nil {
log.Fatal(err)
Expand All @@ -55,4 +63,5 @@ func addGlobalFlags(fs *pflag.FlagSet) {
logflag.LogLevelFlagVar(fs, &logLevel)
runtimeflag.RuntimeVar(fs, &providers.RuntimeName)
networkflag.NetworkPluginVar(fs, &providers.NetworkPluginName)
fs.StringVar(&configPath, "ignite-config", "", "Ignite configuration path; refer to the 'Ignite Configuration' docs for more details")
}
1 change: 1 addition & 0 deletions docs/cli/ignited/ignited.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ TODO: ignited documentation

```
-h, --help help for ignited
--ignite-config string Ignite configuration path; refer to the 'Ignite Configuration' docs for more details
--log-level loglevel Specify the loglevel for the program (default info)
--network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni)
--runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd)
Expand Down
1 change: 1 addition & 0 deletions docs/cli/ignited/ignited_completion.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ignited completion [flags]
### Options inherited from parent commands

```
--ignite-config string Ignite configuration path; refer to the 'Ignite Configuration' docs for more details
--log-level loglevel Specify the loglevel for the program (default info)
--network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni)
--runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd)
Expand Down
1 change: 1 addition & 0 deletions docs/cli/ignited/ignited_daemon.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ ignited daemon [flags]
### Options inherited from parent commands

```
--ignite-config string Ignite configuration path; refer to the 'Ignite Configuration' docs for more details
--log-level loglevel Specify the loglevel for the program (default info)
--network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni)
--runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd)
Expand Down
1 change: 1 addition & 0 deletions docs/cli/ignited/ignited_gitops.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ignited gitops <repo-url> [flags]
### Options inherited from parent commands

```
--ignite-config string Ignite configuration path; refer to the 'Ignite Configuration' docs for more details
--log-level loglevel Specify the loglevel for the program (default info)
--network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni)
--runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd)
Expand Down
1 change: 1 addition & 0 deletions docs/cli/ignited/ignited_version.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ignited version [flags]
### Options inherited from parent commands

```
--ignite-config string Ignite configuration path; refer to the 'Ignite Configuration' docs for more details
--log-level loglevel Specify the loglevel for the program (default info)
--network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni)
--runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd)
Expand Down
53 changes: 53 additions & 0 deletions docs/gitops.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ status:
running: true
```

**NOTE:** `uid` must be set. VM configurations without uid are ignored.

For a more complete example repository configuration, see [luxas/ignite-gitops](https://github.com/luxas/ignite-gitops)

After you have [installed Ignite](installation.md), you can do the following:
Expand All @@ -61,4 +63,55 @@ you need to set up SSH authentication and use the SSH clone URL for now.
Ignite will now search that repo for suitable JSON/YAML files, and apply their state locally.
You should see `my-vm` starting up in `ignite ps`. To enter the VM, run `ignite ssh my-vm`.

### Using a local git repo for testing

Create a new directory and initialize it as a bare git repo. For ignited to
push updates to the git repo, the repo must be created as a bare git repo.

```console
$ mkdir ~/ignite-gitops
$ cd ~/ignite-gitops
$ git init --bare
$ ls
HEAD branches config description hooks info objects refs
```

Clone this git repo and add the sample VM configuration:

```console
$ git clone file:///home/user/ignite-gitops ignite-gitops-clone
Cloning into 'ignite-gitops-clone'...
warning: You appear to have cloned an empty repository.
$ cd ignite-gitops-clone
$ git remote -v
origin file:///home/user/ignite-gitops (fetch)
origin file:///home/user/ignite-gitops (push)
$ # Create my-vm.yaml, commit and push.
```

Run ignited against the bare git repo:

```console
$ sudo ignited gitops file:///home/user/ignite-gitops
INFO[0000] Starting GitOps loop for repo at "file:///home/user/ignite-gitops"
INFO[0000] Whenever changes are pushed to the target branch, Ignite will apply the desired state locally
INFO[0000] Initializing the Git repo...
INFO[0000] Running in read-write mode, will commit back current status to the repo
INFO[0000] Starting the commit loop...
INFO[0000] Starting the checkout loop...
INFO[0000] Starting to clone the repository file:///home/user/ignite-gitops with timeout 1m0s
INFO[0000] New commit observed on branch "master": 7095d3603649792f44110cc5cf7deb0ded897e4b. User initiated: true
INFO[0003] Creating VM "599615df99804ae8" with name "my-vm"...
INFO[0004] Starting VM "599615df99804ae8" with name "my-vm"...
INFO[0004] Networking is handled by "cni"
INFO[0004] Started Firecracker VM "599615df99804ae8" in a container with ID "ignite-599615df99804ae8"
...
INFO[0030] A new commit with the actual state has been created and pushed to the origin: "186a8eb6eced5843480d4a10872db071ecc76439"
INFO[0030] New commit observed on branch "master": 186a8eb6eced5843480d4a10872db071ecc76439. User initiated: false
...
```

**NOTE:** Root permission may be required to push updates to the main repo
because ignited pushes git updates as root.

Please refer to [docs/declarative-config.md](declarative-config.md) for the full API reference.
81 changes: 81 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package config

import (
"fmt"
"os"

log "github.com/sirupsen/logrus"

api "github.com/weaveworks/ignite/pkg/apis/ignite"
"github.com/weaveworks/ignite/pkg/apis/ignite/scheme"
"github.com/weaveworks/ignite/pkg/constants"
"github.com/weaveworks/ignite/pkg/network"
"github.com/weaveworks/ignite/pkg/providers"
"github.com/weaveworks/ignite/pkg/runtime"
)

// ApplyConfiguration merges the given configurations with the default ignite
// configurations.
func ApplyConfiguration(configPath string) error {
var configFilePath string

if configPath != "" {
configFilePath = configPath
} else {
// Check the default config location.
if _, err := os.Stat(constants.IGNITE_CONFIG_FILE); !os.IsNotExist(err) {
log.Debugf("Found default ignite configuration file %s", constants.IGNITE_CONFIG_FILE)
configFilePath = constants.IGNITE_CONFIG_FILE
}
}

if configFilePath != "" {
log.Debugf("Using ignite configuration file %s", configFilePath)
var err error
providers.ComponentConfig, err = getConfigFromFile(configFilePath)
if err != nil {
return err
}

// Set providers runtime and network plugin if found in config
// and not set explicitly via flags.
if providers.ComponentConfig.Spec.Runtime != "" && providers.RuntimeName == "" {
providers.RuntimeName = providers.ComponentConfig.Spec.Runtime
}
if providers.ComponentConfig.Spec.NetworkPlugin != "" && providers.NetworkPluginName == "" {
providers.NetworkPluginName = providers.ComponentConfig.Spec.NetworkPlugin
}
} else {
log.Debugln("Using ignite default configurations")
}

// Set the default runtime and network-plugin if it's not set by
// now.
if providers.RuntimeName == "" {
providers.RuntimeName = runtime.RuntimeContainerd
}
if providers.NetworkPluginName == "" {
providers.NetworkPluginName = network.PluginCNI
}

return nil
}

// getConfigFromFile reads a config file and returns ignite configuration.
func getConfigFromFile(configPath string) (*api.Configuration, error) {
componentConfig := &api.Configuration{}

// TODO: Fix libgitops DecodeFileInto to not allow empty files.
if err := scheme.Serializer.DecodeFileInto(configPath, componentConfig); err != nil {
return nil, err
}

// Ensure the read configuration is valid. If a file contains Kind and
// APIVersion, it's a valid config file. Empty file is invalid.
// NOTE: This is a workaround for libgitops allowing decode of empty file.
if componentConfig.Kind == "" || componentConfig.APIVersion == "" {
return nil, fmt.Errorf("invalid config file, Kind and APIVersion must be set")
}

return componentConfig, nil
}