-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial import of
cloud-orchestrator
(#1)
- Loading branch information
Showing
19 changed files
with
4,224 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,12 +11,29 @@ jobs: | |
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: bufbuild/[email protected] | ||
- uses: arduino/setup-protoc@v2 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v4 | ||
with: | ||
go-version: '1.21' | ||
|
||
- name: Install protoc-gen-go | ||
run: | | ||
go install github.com/golang/protobuf/protoc-gen-go@latest | ||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest | ||
- name: Generating API service protos | ||
run: | | ||
protoc \ | ||
./server/api/v1/pb/service.proto \ | ||
--go_out=. \ | ||
--go_opt=paths=source_relative \ | ||
--go-grpc_out=. \ | ||
--go-grpc_opt=paths=source_relative \ | ||
--proto_path=. | ||
- name: Build | ||
run: go build -v ./... | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
package cloud | ||
|
||
import ( | ||
"errors" | ||
"log" | ||
"os" | ||
"path" | ||
|
||
"github.com/gevulotnetwork/cloud-orchestrator/config" | ||
"github.com/gevulotnetwork/cloud-orchestrator/fs" | ||
"github.com/gevulotnetwork/cloud-orchestrator/ops" | ||
) | ||
|
||
type Orchestrator struct { | ||
configFactory *config.Factory | ||
} | ||
|
||
func NewOrchestrator(cfgFactory *config.Factory) *Orchestrator { | ||
return &Orchestrator{ | ||
configFactory: cfgFactory, | ||
} | ||
} | ||
|
||
func (o *Orchestrator) programToImageName(program string) string { | ||
// Bucket object name has maximum length of 63 and it requires first letter to | ||
// be a character so adjust the hash. | ||
imgNameLen := min(len(program), 62) | ||
return "a" + program[:imgNameLen] | ||
} | ||
|
||
func (o *Orchestrator) PrepareProgramImage(program string, baseImg string) error { | ||
program = o.programToImageName(program) | ||
|
||
// Tempdir needed for files from the original base image. | ||
tmp_dir, err := os.MkdirTemp(os.TempDir(), "gevulot") | ||
if err != nil { | ||
return err | ||
} | ||
defer os.RemoveAll(tmp_dir) | ||
|
||
reader, err := fs.NewReader(baseImg) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = copyAll(reader, tmp_dir, "/") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Generate config for new image. | ||
cfg := o.configFactory.NewConfig(program) | ||
|
||
// Add files from the base image to new one. | ||
cfg.Dirs = append(cfg.Dirs, tmp_dir) | ||
|
||
// Copy program arguments | ||
cfg.Args = reader.ListArgs() | ||
|
||
// Assumption is that there is always at least the arg[0]. | ||
if len(cfg.Args) > 0 { | ||
// Forming a program path this way is a best guess effort.. | ||
cfg.Program = path.Join(tmp_dir, cfg.Args[0]) | ||
cfg.ProgramPath = cfg.Program | ||
|
||
// Ensure that program is set executable. | ||
os.Chmod(cfg.Program, 0755) | ||
} else { | ||
return errors.New("base image doesn't have arguments; can't set config.Program") | ||
} | ||
|
||
// Create temporary file for the generated image. | ||
f, err := os.Create(path.Join(os.TempDir(), program)) | ||
if err != nil { | ||
return err | ||
} | ||
defer f.Close() | ||
defer os.RemoveAll(f.Name()) | ||
|
||
// Copy env variables. Also configure the nanos kernel version if present. | ||
for k, v := range reader.ListEnv() { | ||
if cfg.Env == nil { | ||
cfg.Env = make(map[string]string) | ||
} | ||
|
||
cfg.Env[k] = v | ||
|
||
if k == "NANOS_VERSION" { | ||
cfg.NanosVersion = v | ||
} | ||
} | ||
|
||
cfg.CloudConfig.ImageName = program | ||
cfg.RunConfig.ImageName = f.Name() | ||
|
||
p, ctx, err := ops.Provider(cfg) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
imgFile, err := p.BuildImage(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
defer os.RemoveAll(imgFile) | ||
|
||
log.Printf("build new image: %q", imgFile) | ||
err = p.CreateImage(ctx, imgFile) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (o *Orchestrator) CreateInstance(program string) (string, error) { | ||
program = o.programToImageName(program) | ||
|
||
cfg := o.configFactory.NewConfig(program) | ||
cfg.CloudConfig.ImageName = program | ||
cfg.RunConfig.InstanceName = program | ||
|
||
p, ctx, err := ops.Provider(cfg) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
// TODO: Figure out if we actually need to pick up the right kernel version | ||
// from the image and configure it per program config? | ||
cfg.RunConfig.Kernel = cfg.Kernel | ||
|
||
log.Printf("creating instance on %s\n", cfg.CloudConfig.Platform) | ||
err = p.CreateInstance(ctx) | ||
if err != nil { | ||
return "", err | ||
} | ||
log.Printf("create instance %q on %s\n", cfg.RunConfig.InstanceName, cfg.CloudConfig.Platform) | ||
|
||
return cfg.RunConfig.InstanceName, nil | ||
} | ||
|
||
func (o *Orchestrator) DeleteInstance(program string) error { | ||
program = o.programToImageName(program) | ||
|
||
cfg := o.configFactory.NewConfig(program) | ||
cfg.CloudConfig.ImageName = program | ||
cfg.RunConfig.InstanceName = program | ||
|
||
p, ctx, err := ops.Provider(cfg) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
log.Printf("deleting instance %q on %s\n", cfg.RunConfig.InstanceName, cfg.CloudConfig.Platform) | ||
err = p.DeleteInstance(ctx, program) | ||
if err != nil { | ||
return err | ||
} | ||
log.Printf("deleted instance %q on %s\n", cfg.RunConfig.InstanceName, cfg.CloudConfig.Platform) | ||
|
||
return nil | ||
} | ||
|
||
func copyAll(reader *fs.Reader, dstPath string, curPath string) error { | ||
fileEntries, err := reader.ReadDir(curPath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, entry := range fileEntries { | ||
fullSrcPath := path.Join(curPath, entry.Name()) | ||
fullDstPath := path.Join(dstPath, entry.Name()) | ||
|
||
if entry.IsDir() { | ||
err = os.MkdirAll(fullDstPath, 0755) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Recurse into sub-directories. | ||
err = copyAll(reader, fullDstPath, fullSrcPath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
continue | ||
} | ||
|
||
err = reader.CopyFile(fullSrcPath, fullDstPath, true) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func min(a, b int) int { | ||
if a < b { | ||
return a | ||
} | ||
|
||
return b | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
platform = "gcp" | ||
|
||
[gcp] | ||
bucket = "<bucket name for images>" | ||
project_id = "<gcp project id>" | ||
zone = "<gcp zone>" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package config | ||
|
||
import ( | ||
"log" | ||
"sync" | ||
|
||
"github.com/nanovms/ops/cmd" | ||
api "github.com/nanovms/ops/lepton" | ||
"github.com/nanovms/ops/types" | ||
"github.com/spf13/pflag" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
var readOnce sync.Once | ||
|
||
func readConfig() { | ||
viper.SetConfigType("toml") | ||
viper.SetConfigName("config") | ||
viper.AddConfigPath("/etc/gevulot") | ||
viper.AddConfigPath("$HOME/.config/gevulot") | ||
viper.AddConfigPath("$HOME/.gevulot") | ||
viper.AddConfigPath(".") | ||
err := viper.ReadInConfig() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
func init() { | ||
// Read config only once. | ||
readOnce.Do(readConfig) | ||
} | ||
|
||
type Factory struct { | ||
defaults *cmd.MergeConfigContainer | ||
platformConfigurator PlatformConfig | ||
} | ||
|
||
// Platform defines the cloud provider, currently supporting aws, azure, and gcp. | ||
func NewFactory() *Factory { | ||
platformCfg, err := getPlatformConfig() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Construct all default config handlers. | ||
flags := newFlags() | ||
|
||
// Declare flags. | ||
cmd.PersistBuildImageCommandFlags(flags) | ||
cmd.PersistConfigCommandFlags(flags) | ||
cmd.PersistGlobalCommandFlags(flags) | ||
cmd.PersistNanosVersionCommandFlags(flags) | ||
cmd.PersistNightlyCommandFlags(flags) | ||
cmd.PersistPkgCommandFlags(flags) | ||
cmd.PersistProviderCommandFlags(flags) | ||
|
||
// Set some platform specific settings. | ||
platformCfg.SetFlags(flags) | ||
|
||
// Initialize config containers. | ||
configFlags := cmd.NewConfigCommandFlags(flags) | ||
globalFlags := cmd.NewGlobalCommandFlags(flags) | ||
nightlyFlags := cmd.NewNightlyCommandFlags(flags) | ||
nanosVersionFlags := cmd.NewNanosVersionCommandFlags(flags) | ||
buildImageFlags := cmd.NewBuildImageCommandFlags(flags) | ||
providerFlags := cmd.NewProviderCommandFlags(flags) | ||
pkgFlags := cmd.NewPkgCommandFlags(flags) | ||
|
||
return &Factory{ | ||
defaults: cmd.NewMergeConfigContainer(configFlags, globalFlags, nightlyFlags, nanosVersionFlags, buildImageFlags, providerFlags, pkgFlags), | ||
platformConfigurator: platformCfg, | ||
} | ||
} | ||
|
||
func getPlatformConfig() (PlatformConfig, error) { | ||
var platform PlatformConfig | ||
|
||
switch viper.GetString("platform") { | ||
case "aws": | ||
platform = &AWSPlatformConfig{} | ||
case "gcp": | ||
platform = &GCPPlatformConfig{} | ||
case "azure": | ||
log.Fatal("Azure is not supported at the moment") | ||
} | ||
|
||
err := platform.load() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return platform, nil | ||
} | ||
|
||
// TODO: program param is reserved for program specific configuration lookups. | ||
func (f *Factory) NewConfig(program string) *types.Config { | ||
cfg := api.NewConfig() | ||
err := f.defaults.Merge(cfg) | ||
if err != nil { | ||
// There are no user passed parameters involved here so there should be no | ||
// way for `Merge(cfg)` to return an error here. | ||
log.Fatalf("failed to default config: %#v", err) | ||
} | ||
|
||
// Finalize last minute adjustments to config ;) | ||
f.platformConfigurator.FinalizeConfig(cfg) | ||
|
||
return cfg | ||
} | ||
|
||
func newFlags() *pflag.FlagSet { | ||
// TODO: Figure out if flags.SetOutput(new(bytes.Buffer)) is needed. | ||
return pflag.NewFlagSet("<placeholder>", pflag.ContinueOnError) | ||
} |
Oops, something went wrong.