func (e *Command) repoCreate(c *cobra.Command, args []string) { if len(args) < 1 { Fail(1, "Valid arguments: <id> [<clone repo url>]\n") } t := e.Transport.Get() id, err := NewResourceLocator(t, git.ResourceTypeRepository, args[0]) if err != nil { Fail(1, "You must pass one valid repository name: %s\n", err.Error()) } if id.(*ResourceLocator).Type != git.ResourceTypeRepository { Fail(1, "You must pass one valid repository name: %s\n", err.Error()) } cloneUrl := "" if len(args) == 2 { cloneUrl = args[1] } Executor{ On: Locators{id}, Serial: func(on Locator) JobRequest { return &gitjobs.CreateRepositoryRequest{ Id: git.RepoIdentifier(on.(*ResourceLocator).Id), CloneUrl: cloneUrl, RequestId: jobs.NewRequestIdentifier(), } }, Output: os.Stdout, Transport: t, }.StreamAndExit() }
func repoCreate(c *cobra.Command, args []string) { if len(args) < 1 { Fail(1, "Valid arguments: <id> [<clone repo url>]\n") } t := c.Flags().Lookup("transport").Value.(*transport.TransportFlag).Get() id, err := NewResourceLocator(t, git.ResourceTypeRepository, args[0]) if err != nil { Fail(1, "You must pass one valid repository name: %s\n", err.Error()) } if id.(*ResourceLocator).Type != git.ResourceTypeRepository { Fail(1, "You must pass one valid repository name: %s\n", err.Error()) } cloneUrl := "" if len(args) == 2 { cloneUrl = args[1] } Executor{ On: Locators{id}, Serial: func(on Locator) jobs.Job { return &gitjobs.CreateRepositoryRequest{ Id: git.RepoIdentifier(on.(*ResourceLocator).Id), CloneUrl: cloneUrl, RequestId: jobs.NewRequestIdentifier(), } }, Output: os.Stdout, LocalInit: LocalInitializers(systemd.Start, containers.InitializeData), Transport: t, }.StreamAndExit() }
func (ctx *CommandContext) installImage(c *cobra.Command, args []string) { if err := ctx.environment.ExtractVariablesFrom(&args, true); err != nil { cmd.Fail(1, err.Error()) } if len(args) < 2 { cmd.Fail(1, "Valid arguments: <image_name> <id> ...") } t := ctx.Transport.Get() imageId := args[0] if imageId == "" { cmd.Fail(1, "Argument 1 must be a Docker image to base the service on") } ids, err := cloc.NewContainerLocators(t, args[1:]...) if err != nil { cmd.Fail(1, "You must pass one or more valid service names: %s", err.Error()) } for _, locator := range ids { if imageId == string(cloc.AsIdentifier(locator)) { cmd.Fail(1, "Image name and container id must not be the same: %s", imageId) } } cmd.Executor{ On: ids, Serial: func(on cmd.Locator) cmd.JobRequest { r := cjobs.InstallContainerRequest{ RequestIdentifier: jobs.NewRequestIdentifier(), Id: cloc.AsIdentifier(on), Image: imageId, Started: ctx.start, Isolate: ctx.isolate, SocketActivation: ctx.sockAct, Ports: *ctx.portPairs.Get().(*port.PortPairs), Environment: &ctx.environment.Description, NetworkLinks: ctx.networkLinks.NetworkLinks, VolumeConfig: ctx.volumeConfig.VolumeConfig, SystemdSlice: ctx.systemdSlice, } return &r }, Output: os.Stdout, Transport: t, }.StreamAndExit() }
func installImage(cmd *cobra.Command, args []string) { if err := environment.ExtractVariablesFrom(&args, true); err != nil { Fail(1, err.Error()) } if len(args) < 2 { Fail(1, "Valid arguments: <image_name> <id> ...\n") } imageId := args[0] if imageId == "" { Fail(1, "Argument 1 must be a Docker image to base the service on\n") } ids, err := NewContainerLocators(args[1:]...) if err != nil { Fail(1, "You must pass one or more valid service names: %s\n", err.Error()) } suffix := "/" + imageId for _, locator := range ids { if strings.HasSuffix(locator.Identity(), suffix) { Fail(1, "Image name and container id must not be the same: %s\n", imageId) } } Executor{ On: ids, Serial: func(on Locator) jobs.Job { return &http.HttpInstallContainerRequest{ InstallContainerRequest: jobs.InstallContainerRequest{ RequestIdentifier: jobs.NewRequestIdentifier(), Id: on.(ResourceLocator).Identifier(), Image: imageId, Started: start, Isolate: isolate, SocketActivation: sockAct, Ports: *portPairs.Get().(*port.PortPairs), Environment: &environment.Description, NetworkLinks: networkLinks.NetworkLinks, }, } }, Output: os.Stdout, LocalInit: needsSystemdAndData, }.StreamAndExit() }
func (t *TokenConfiguration) Sign(job *cjobs.ContentRequest, keyId string, expiration int64) (string, error) { source := &TokenData{ Identifier: jobs.NewRequestIdentifier().String(), Locator: job.Locator, Type: job.Type, ExpirationDate: expiration, } buf := &bytes.Buffer{} encoder := json.NewEncoder(buf) if err := encoder.Encode(source); err != nil { return "", err } cipher, err := rsa.EncryptPKCS1v15(rand.Reader, t.publicKey, buf.Bytes()) if err != nil { return "", err } hash := crypto.SHA256.New() if _, err := hash.Write(cipher); err != nil { return "", err } hashed := hash.Sum(nil) sig, err := rsa.SignPKCS1v15(rand.Reader, t.privateKey, crypto.SHA256, hashed) if err != nil { return "", err } return fmt.Sprintf( "%s/%s/%s", utils.EncodeUrlPath(keyId), base64.URLEncoding.EncodeToString(sig), base64.URLEncoding.EncodeToString(cipher), ), nil }
func (ctx *CommandContext) deployContainers(c *cobra.Command, args []string) { if len(args) < 1 { cmd.Fail(1, "Valid arguments: <deployment_file|URL> <host> ...") } t := ctx.Transport.Get() path := args[0] if path == "" { cmd.Fail(1, "Argument 1 must be deployment file or URL describing how the containers are related") } u, err := url.Parse(path) if nil != err { cmd.Fail(1, "Cannot Parse Argument 1: %s", err.Error()) } var deploy *deployment.Deployment switch u.Scheme { case "": deploy, err = deployment.NewDeploymentFromFile(u.Path) case "file": deploy, err = deployment.NewDeploymentFromFile(u.Path) case "http", "https": deploy, err = deployment.NewDeploymentFromURL(u.String(), *ctx.Insecure, time.Duration(ctx.timeout)) default: cmd.Fail(1, "Unsupported URL Scheme '%s' for deployment", u.Scheme) } if nil != err { cmd.Fail(1, "Unable to load deployment from %s: %s", path, err.Error()) } if len(args) == 1 { args = append(args, transport.Local.String()) } servers, err := transport.NewTransportLocators(t, args[1:]...) if err != nil { cmd.Fail(1, "You must pass zero or more valid host names (use '%s' or pass no arguments for the current server): %s", transport.Local.String(), err.Error()) } re := regexp.MustCompile("\\.\\d{8}\\-\\d{6}\\z") now := time.Now().Format(".20060102-150405") base := filepath.Base(path) base = re.ReplaceAllString(base, "") newPath := base + now fmt.Printf("==> Deploying %s\n", path) changes, removed, err := deploy.Describe(deployment.SimplePlacement(servers), t) if err != nil { cmd.Fail(1, "Deployment is not valid: %s", err.Error()) } if len(removed) > 0 { removedIds, err := LocatorsForDeploymentInstances(t, removed) if err != nil { cmd.Fail(1, "Unable to generate deployment info: %s", err.Error()) } failures := cmd.Executor{ On: removedIds, Serial: func(on cmd.Locator) cmd.JobRequest { return &cjobs.DeleteContainerRequest{ Id: cloc.AsIdentifier(on), } }, Output: os.Stdout, OnSuccess: func(r *cmd.CliJobResponse, w io.Writer, job cmd.RequestedJob) { fmt.Fprintf(w, "==> Deleted %s", string(job.Request.(*cjobs.DeleteContainerRequest).Id)) }, Transport: t, }.Stream() for i := range failures { fmt.Fprintf(os.Stderr, failures[i].Error()) } } addedIds, err := LocatorsForDeploymentInstances(t, changes.Instances.Added()) if err != nil { cmd.Fail(1, "Unable to generate deployment info: %s", err.Error()) } errors := cmd.Executor{ On: addedIds, Serial: func(on cmd.Locator) cmd.JobRequest { instance, _ := changes.Instances.Find(cloc.AsIdentifier(on)) links := instance.NetworkLinks() return &cjobs.InstallContainerRequest{ RequestIdentifier: jobs.NewRequestIdentifier(), Id: instance.Id, Image: instance.Image, Environment: instance.EnvironmentVariables(), Isolate: ctx.isolate, Ports: instance.Ports.PortPairs(), NetworkLinks: &links, } }, OnSuccess: func(r *cmd.CliJobResponse, w io.Writer, job cmd.RequestedJob) { installJob := job.Request.(*cjobs.InstallContainerRequest) instance, _ := changes.Instances.Find(installJob.Id) if pairs, ok := installJob.PortMappingsFrom(r.Pending); ok { if !instance.Ports.Update(pairs) { fmt.Fprintf(os.Stderr, "Not all ports listed %+v were returned by the server %+v", instance.Ports, pairs) } } }, Output: os.Stdout, Transport: t, }.Stream() changes.UpdateLinks() for _, c := range changes.Containers { instances := c.Instances() if len(instances) > 0 { for _, link := range instances[0].NetworkLinks() { fmt.Printf("==> Linking %s: %s:%d -> %s:%d\n", c.Name, link.FromHost, link.FromPort, link.ToHost, link.ToPort) } } } contents, _ := json.MarshalIndent(changes, "", " ") contents = append(contents, []byte("\n")...) if err := ioutil.WriteFile(newPath, contents, 0664); err != nil { fmt.Fprintf(os.Stderr, "Unable to write %s: %s\n", newPath, err.Error()) } linkedIds, err := LocatorsForDeploymentInstances(t, changes.Instances.Linked()) if err != nil { cmd.Fail(1, "Unable to generate deployment info: %s", err.Error()) } cmd.Executor{ On: linkedIds, Group: func(on ...cmd.Locator) cmd.JobRequest { links := []containers.ContainerLink{} for i := range on { instance, _ := changes.Instances.Find(cloc.AsIdentifier(on[i])) network := instance.NetworkLinks() if len(network) > 0 { links = append(links, containers.ContainerLink{instance.Id, network}) } } return &cjobs.LinkContainersRequest{&containers.ContainerLinks{links}} }, Output: os.Stdout, Transport: t, }.Stream() cmd.Executor{ On: addedIds, Serial: func(on cmd.Locator) cmd.JobRequest { return &cjobs.StartedContainerStateRequest{ Id: cloc.AsIdentifier(on), } }, Output: os.Stdout, Transport: t, }.Stream() fmt.Printf("==> Deployed as %s\n", newPath) if len(errors) > 0 { for i := range errors { fmt.Fprintf(os.Stderr, "Error: %s\n", errors[i]) } os.Exit(1) } }
func (conf *HttpConfiguration) handleWithMethod(method JobHandler) func(*rest.ResponseWriter, *rest.Request) { return func(w *rest.ResponseWriter, r *rest.Request) { match := r.Header.Get("If-Match") segments := strings.Split(match, ",") for i := range segments { if strings.HasPrefix(segments[i], "api=") { if segments[i][4:] != ApiVersion() { http.Error(w, fmt.Sprintf("Current API version %s does not match requested %s", ApiVersion(), segments[i][4:]), http.StatusPreconditionFailed) return } } } context := &jobs.JobContext{} requestId := r.Header.Get("X-Request-Id") if requestId == "" { context.Id = jobs.NewRequestIdentifier() } else { id, err := jobs.NewRequestIdentifierFromString(requestId) if err != nil { http.Error(w, "X-Request-Id must be a 32 character hexadecimal string", http.StatusBadRequest) return } context.Id = id } /*token, id, errt := extractToken(r.PathParam("token"), r.Request) if errt != nil { log.Println(errt) http.Error(w, "Token is required - pass /token/<token>/<path>", http.StatusForbidden) return } if token.D == 0 { log.Println("http: Recommend passing 'd' as an argument for the current date") } if token.U == "" { log.Println("http: Recommend passing 'u' as an argument for the associated user") }*/ job, errh := method(context, r) if errh != nil { if errh != ErrHandledResponse { http.Error(w, "Invalid request: "+errh.Error()+"\n", http.StatusBadRequest) } return } mode := ResponseJson if r.Header.Get("Accept") == "text/plain" { mode = ResponseTable } acceptHeader := r.Header.Get("Accept") overrideAcceptHeader := r.Header.Get("X-Accept") if overrideAcceptHeader != "" { acceptHeader = overrideAcceptHeader } canStream := didClientRequestStreamableResponse(acceptHeader) if streaming, ok := job.(HttpStreamable); ok { canStream = streaming.Streamable() } response := NewHttpJobResponse(w.ResponseWriter, !canStream, mode) wait, errd := conf.Dispatcher.Dispatch(context.Id, job, response) if errd == jobs.ErrRanToCompletion { http.Error(w, errd.Error(), http.StatusNoContent) return } else if errd != nil { serveRequestError(w, apiRequestError{errd, errd.Error(), http.StatusServiceUnavailable}) return } <-wait } }
func (h *HttpTransport) ExecuteRemote(baseUrl *url.URL, job RemoteExecutable, res jobs.Response) error { reader, writer := io.Pipe() httpreq, errn := http.NewRequest(job.HttpMethod(), baseUrl.String(), reader) if errn != nil { return errn } id := job.MarshalRequestIdentifier() if len(id) == 0 { id = jobs.NewRequestIdentifier() } query := &url.Values{} job.MarshalUrlQuery(query) req := httpreq req.Header.Set("X-Request-Id", id.String()) req.Header.Set("If-Match", "api="+ApiVersion()) req.Header.Set("Content-Type", "application/json") //TODO: introduce API version per job //TODO: content request signing for GETs req.URL.Path = job.HttpPath() req.URL.RawQuery = query.Encode() go func() { if err := job.MarshalHttpRequestBody(writer); err != nil { log.Printf("http_remote: Error when writing to http: %v", err) writer.CloseWithError(err) } else { writer.Close() } }() resp, err := h.client.Do(req) if err != nil { return err } defer resp.Body.Close() isJson := resp.Header.Get("Content-Type") == "application/json" switch code := resp.StatusCode; { case code == 202: if isJson { return errors.New("Decoding of streaming JSON has not been implemented") } data, err := job.UnmarshalHttpResponse(resp.Header, nil, ResponseTable) if err != nil { return err } if pending, ok := data.(map[string]interface{}); ok { for k := range pending { res.WritePendingSuccess(k, pending[k]) } } w := res.SuccessWithWrite(jobs.ResponseOk, false, false) if _, err := io.Copy(w, resp.Body); err != nil { return err } case code == 204: data, err := job.UnmarshalHttpResponse(resp.Header, nil, ResponseTable) if err != nil { return err } if pending, ok := data.(map[string]interface{}); ok { for k := range pending { res.WritePendingSuccess(k, pending[k]) } } res.Success(jobs.ResponseOk) case code >= 200 && code < 300: if !isJson { return errors.New(fmt.Sprintf("remote: Response with %d status code had content type %s (should be application/json)", code, resp.Header.Get("Content-Type"))) } data, err := job.UnmarshalHttpResponse(nil, resp.Body, ResponseJson) if err != nil { return err } res.SuccessWithData(jobs.ResponseOk, data) default: if isJson { decoder := json.NewDecoder(resp.Body) data := httpFailureResponse{} if err := decoder.Decode(&data); err != nil { return err } res.Failure(jobs.SimpleError{jobs.ResponseError, data.Message}) return nil } io.Copy(os.Stderr, resp.Body) res.Failure(jobs.SimpleError{jobs.ResponseError, "Unable to decode response."}) } return nil }
func deployContainers(cmd *cobra.Command, args []string) { if len(args) < 1 { Fail(1, "Valid arguments: <deployment_file> <host> ...") } path := args[0] if path == "" { Fail(1, "Argument 1 must be deployment file describing how the containers are related") } deploy, err := deployment.NewDeploymentFromFile(path) if err != nil { Fail(1, "Unable to load deployment file: %s", err.Error()) } if len(args) == 1 { args = append(args, transport.Local.String()) } servers, err := transport.NewTransportLocators(defaultTransport.Get(), args[1:]...) if err != nil { Fail(1, "You must pass zero or more valid host names (use '%s' or pass no arguments for the current server): %s", transport.Local.String(), err.Error()) } re := regexp.MustCompile("\\.\\d{8}\\-\\d{6}\\z") now := time.Now().Format(".20060102-150405") base := filepath.Base(path) base = re.ReplaceAllString(base, "") newPath := base + now fmt.Printf("==> Deploying %s\n", path) changes, removed, err := deploy.Describe(deployment.SimplePlacement(servers), defaultTransport.Get()) if err != nil { Fail(1, "Deployment is not valid: %s", err.Error()) } if len(removed) > 0 { removedIds, err := LocatorsForDeploymentInstances(defaultTransport.Get(), removed) if err != nil { Fail(1, "Unable to generate deployment info: %s", err.Error()) } failures := Executor{ On: removedIds, Serial: func(on Locator) jobs.Job { return &cjobs.DeleteContainerRequest{ Id: AsIdentifier(on), Label: on.Identity(), } }, Output: os.Stdout, OnSuccess: func(r *CliJobResponse, w io.Writer, job interface{}) { fmt.Fprintf(w, "==> Deleted %s", job.(jobs.LabeledJob).JobLabel()) }, LocalInit: needsSystemdAndData, Transport: defaultTransport.Get(), }.Stream() for i := range failures { fmt.Fprintf(os.Stderr, failures[i].Error()) } } addedIds, err := LocatorsForDeploymentInstances(defaultTransport.Get(), changes.Instances.Added()) if err != nil { Fail(1, "Unable to generate deployment info: %s", err.Error()) } errors := Executor{ On: addedIds, Serial: func(on Locator) jobs.Job { instance, _ := changes.Instances.Find(AsIdentifier(on)) links := instance.NetworkLinks() return &cjobs.InstallContainerRequest{ RequestIdentifier: jobs.NewRequestIdentifier(), Id: instance.Id, Image: instance.Image, Isolate: isolate, Ports: instance.Ports.PortPairs(), NetworkLinks: &links, } }, OnSuccess: func(r *CliJobResponse, w io.Writer, job interface{}) { installJob := job.(*cjobs.InstallContainerRequest) instance, _ := changes.Instances.Find(installJob.Id) if pairs, ok := installJob.PortMappingsFrom(r.Pending); ok { if !instance.Ports.Update(pairs) { fmt.Fprintf(os.Stderr, "Not all ports listed %+v were returned by the server %+v", instance.Ports, pairs) } } }, Output: os.Stdout, LocalInit: needsSystemdAndData, Transport: defaultTransport.Get(), }.Stream() changes.UpdateLinks() for _, c := range changes.Containers { instances := c.Instances() if len(instances) > 0 { for _, link := range instances[0].NetworkLinks() { fmt.Printf("==> Linking %s: %s:%d -> %s:%d\n", c.Name, link.FromHost, link.FromPort, link.ToHost, link.ToPort) } } } contents, _ := json.Marshal(changes) if err := ioutil.WriteFile(newPath, contents, 0664); err != nil { fmt.Fprintf(os.Stderr, "Unable to write %s: %s\n", newPath, err.Error()) } linkedIds, err := LocatorsForDeploymentInstances(defaultTransport.Get(), changes.Instances.Linked()) if err != nil { Fail(1, "Unable to generate deployment info: %s", err.Error()) } Executor{ On: linkedIds, Group: func(on ...Locator) jobs.Job { links := []cjobs.ContainerLink{} for i := range on { instance, _ := changes.Instances.Find(AsIdentifier(on[i])) network := instance.NetworkLinks() if len(network) > 0 { links = append(links, cjobs.ContainerLink{instance.Id, network}) } } return &cjobs.LinkContainersRequest{&cjobs.ContainerLinks{links}, on[0].TransportLocator().String()} }, Output: os.Stdout, Transport: defaultTransport.Get(), }.Stream() Executor{ On: addedIds, Serial: func(on Locator) jobs.Job { return &cjobs.StartedContainerStateRequest{ Id: AsIdentifier(on), } }, Output: os.Stdout, Transport: defaultTransport.Get(), }.Stream() fmt.Printf("==> Deployed as %s\n", newPath) if len(errors) > 0 { for i := range errors { fmt.Fprintf(os.Stderr, "Error: %s\n", errors[i]) } os.Exit(1) } }
func (conf *HttpConfiguration) handleWithMethod(method JobHandler) func(*rest.ResponseWriter, *rest.Request) { return func(w *rest.ResponseWriter, r *rest.Request) { match := r.Header.Get("If-Match") segments := strings.Split(match, ",") for i := range segments { if strings.HasPrefix(segments[i], "api=") { if segments[i][4:] != ApiVersion() { http.Error(w, fmt.Sprintf("Current API version %s does not match requested %s", ApiVersion(), segments[i][4:]), http.StatusPreconditionFailed) return } } } context := &jobs.JobContext{} requestId := r.Header.Get("X-Request-Id") if requestId == "" { context.Id = jobs.NewRequestIdentifier() } else { id, err := jobs.NewRequestIdentifierFromString(requestId) if err != nil { http.Error(w, "X-Request-Id must be a 32 character hexadecimal string", http.StatusBadRequest) return } context.Id = id } // parse the incoming request into an object jobRequest, errh := method(context, r) if errh != nil { serveRequestError(w, apiRequestError{errh, errh.Error(), http.StatusBadRequest}) return } // find the job implementation for that request job, errj := jobs.JobFor(jobRequest) if errj != nil { serveRequestError(w, apiRequestError{errj, errj.Error(), http.StatusBadRequest}) return } // determine the type of the request acceptHeader := r.Header.Get("Accept") overrideAcceptHeader := r.Header.Get("X-Accept") if overrideAcceptHeader != "" { acceptHeader = overrideAcceptHeader } // setup the appropriate mode mode := ResponseJson if acceptHeader == "text/plain" { mode = ResponseTable } canStream := didClientRequestStreamableResponse(acceptHeader) response := NewHttpJobResponse(w.ResponseWriter, !canStream, mode) // queue / handle the request wait, errd := conf.Dispatcher.Dispatch(context.Id, job, response) if errd == jobs.ErrRanToCompletion { http.Error(w, errd.Error(), http.StatusNoContent) return } else if errd != nil { serveRequestError(w, apiRequestError{errd, errd.Error(), http.StatusServiceUnavailable}) return } <-wait } }
func (conf *HttpConfiguration) handleWithMethod(method JobHandler) func(*rest.ResponseWriter, *rest.Request) { return func(w *rest.ResponseWriter, r *rest.Request) { context := &HttpContext{} context.ApiVersion = r.Header.Get("X-Api-Version") requestId := r.Header.Get("X-Request-Id") if requestId == "" { context.Id = jobs.NewRequestIdentifier() } else { id, err := jobs.NewRequestIdentifierFromString(requestId) if err != nil { http.Error(w, "X-Request-Id must be a 32 character hexadecimal string", http.StatusBadRequest) return } context.Id = id } // parse the incoming request into an object jobRequest, errh := method(conf, context, r) if errh != nil { serveRequestError(w, apiRequestError{errh, errh.Error(), http.StatusBadRequest}) return } // find the job implementation for that request job, errj := jobs.JobFor(jobRequest) if errj != nil { if errj == jobs.ErrNoJobForRequest { serveRequestError(w, apiRequestError{errj, fmt.Sprintf("The requested job %s has no registered implementation", reflect.TypeOf(jobRequest)), http.StatusBadRequest}) } serveRequestError(w, apiRequestError{errj, errj.Error(), http.StatusBadRequest}) return } // determine the type of the request acceptHeader := r.Header.Get("Accept") overrideAcceptHeader := r.Header.Get("X-Accept") if overrideAcceptHeader != "" { acceptHeader = overrideAcceptHeader } // setup the appropriate mode mode := client.ResponseJson if acceptHeader == "text/plain" { mode = client.ResponseTable } canStream := didClientRequestStreamableResponse(acceptHeader) response := NewHttpJobResponse(w.ResponseWriter, !canStream, mode) // queue / handle the request wait, errd := conf.Dispatcher.Dispatch(context.Id, job, response) if errd == jobs.ErrRanToCompletion { http.Error(w, errd.Error(), http.StatusNoContent) return } else if errd != nil { serveRequestError(w, apiRequestError{errd, errd.Error(), http.StatusServiceUnavailable}) return } <-wait } }