Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add update agent example application #8

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
19 changes: 19 additions & 0 deletions custom-update-agent/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM --platform=$BUILDPLATFORM golang:alpine AS builder
RUN apk update && apk add --no-cache git
RUN apk update && apk add --no-cache ca-certificates
WORKDIR /build

COPY . .
RUN go mod download

ARG TARGETOS TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o app .

FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
k-gostev marked this conversation as resolved.
Show resolved Hide resolved
WORKDIR /source
COPY . .
WORKDIR /bin
COPY --from=builder /build/app .
WORKDIR /source
CMD ["/bin/app"]
Empty file.
23 changes: 23 additions & 0 deletions custom-update-agent/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module github.com/eclipse-kanto/example-applications/custom-update-agent

go 1.18
k-gostev marked this conversation as resolved.
Show resolved Hide resolved

replace github.com/docker/docker => github.com/moby/moby v23.0.3+incompatible

require (
github.com/eclipse-kanto/container-management v0.1.0-M4
github.com/eclipse-kanto/update-manager v0.0.0-20230628072101-b91f0c30e00f
dimitar-dimitrow marked this conversation as resolved.
Show resolved Hide resolved
github.com/pkg/errors v0.9.1
github.com/rickar/props v1.0.0
)

require (
github.com/eclipse/paho.mqtt.golang v1.4.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
)
47 changes: 47 additions & 0 deletions custom-update-agent/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse-kanto/container-management v0.1.0-M4 h1:061GDDWH+3pW7rn5OHiJ5VT9fodjz3pVnGW59j1qcnQ=
github.com/eclipse-kanto/container-management v0.1.0-M4/go.mod h1:CpoavUZrXKGNYrRNGnvSLZEBkx/K3gl4DpY61YSpAdU=
github.com/eclipse-kanto/update-manager v0.0.0-20230628072101-b91f0c30e00f h1:+oeyDIcFCE4cnczhQp7UycS9jZNUuml2x6YccVGjIaY=
github.com/eclipse-kanto/update-manager v0.0.0-20230628072101-b91f0c30e00f/go.mod h1:wzaUsK5uU6OgdarSTYyV8Wakb3MZ3ifOUR9FNGfoYcA=
github.com/eclipse/paho.mqtt.golang v1.4.1 h1:tUSpviiL5G3P9SZZJPC4ZULZJsxQKXxfENpMvdbAXAI=
github.com/eclipse/paho.mqtt.golang v1.4.1/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rickar/props v1.0.0 h1:3C3j+wF2/XbQ/sCGRK8DkCLwuRvzqToMvDzmdxHwCsg=
github.com/rickar/props v1.0.0/go.mod h1:VVywBJXdOY3IwDtBmgAMIZs/XM/CtMKSJzu5dsHYwEY=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
93 changes: 93 additions & 0 deletions custom-update-agent/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) 2024 Contributors to the Eclipse Foundation
//
// See the NOTICE file(s) distributed with this work for additional
// information regarding copyright ownership.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0

package main

import (
"context"
"log/slog"
"os"
"os/signal"
"syscall"
"time"

"github.com/eclipse-kanto/example-applications/custom-update-agent/updateagent"
"github.com/eclipse-kanto/example-applications/custom-update-agent/util"

"github.com/eclipse-kanto/update-manager/api"
)

const (

// default local connection config
k-gostev marked this conversation as resolved.
Show resolved Hide resolved
connectionBrokerURLDefault = "tcp://localhost:1883"
connectionKeepAliveDefault = "20s"
connectionDisconnectTimeoutDefault = "250ms"
connectionClientUsername = ""
connectionClientPassword = ""
connectTimeoutTimeoutDefault = "30s"
acknowledgeTimeoutDefault = "15s"
subscribeTimeoutDefault = "15s"
unsubscribeTimeoutDefault = "5s"
dimitar-dimitrow marked this conversation as resolved.
Show resolved Hide resolved

// default update agent config
k-gostev marked this conversation as resolved.
Show resolved Hide resolved
updateAgentEnableDefault = false
dimitar-dimitrow marked this conversation as resolved.
Show resolved Hide resolved
updateAgentDomainDefault = "files"
updateAgentVerboseInventoryReportDefault = false
dimitar-dimitrow marked this conversation as resolved.
Show resolved Hide resolved
)

func parseDuration(duration string) time.Duration {
d, _ := time.ParseDuration(duration)
return d
}

func getDefaultCustomUpdateAgentOpts() []updateagent.FileUpdateAgentOpt {
updateAgentOpts := []updateagent.FileUpdateAgentOpt{}

updateAgentOpts = append(updateAgentOpts,
updateagent.WithDomainName(updateAgentDomainDefault),
updateagent.WithConnectionBroker(connectionBrokerURLDefault),
updateagent.WithConnectionKeepAlive(parseDuration(connectionKeepAliveDefault)),
updateagent.WithConnectionDisconnectTimeout(parseDuration(connectionDisconnectTimeoutDefault)),
updateagent.WithConnectionClientUsername(connectionClientUsername),
updateagent.WithConnectionClientPassword(connectionClientPassword),
updateagent.WithConnectionConnectTimeout(parseDuration(connectTimeoutTimeoutDefault)),
updateagent.WithConnectionAcknowledgeTimeout(parseDuration(acknowledgeTimeoutDefault)),
updateagent.WithConnectionSubscribeTimeout(parseDuration(subscribeTimeoutDefault)),
updateagent.WithConnectionUnsubscribeTimeout(parseDuration(unsubscribeTimeoutDefault)))

return updateAgentOpts
}

func main() {

//set logger level and output
k-gostev marked this conversation as resolved.
Show resolved Hide resolved
logger := util.ConfigLogger(slog.LevelDebug, os.Stdout)
slog.SetDefault(&logger)

updateAgent, _ := updateagent.Init(getDefaultCustomUpdateAgentOpts())

err := updateAgent.(api.UpdateAgent).Start(context.Background())
if err != nil {
slog.Error("could not start Update Agent service! got", "error", err)
} else {
slog.Info("successfully started Update Agent service")
}
k-gostev marked this conversation as resolved.
Show resolved Hide resolved

var signalChan = make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGHUP)

sig := <-signalChan
slog.Info("Exiting!, recieved", "signal", sig)
updateAgent.(api.UpdateAgent).Stop()

k-gostev marked this conversation as resolved.
Show resolved Hide resolved
}
55 changes: 55 additions & 0 deletions custom-update-agent/updateagent/internal_desired_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) 2024 Contributors to the Eclipse Foundation
//
// See the NOTICE file(s) distributed with this work for additional
// information regarding copyright ownership.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0

package updateagent

import (
"fmt"

"github.com/eclipse-kanto/example-applications/custom-update-agent/util"

"github.com/eclipse-kanto/update-manager/api/types"
"github.com/pkg/errors"
)

type internalDesiredState struct {
desiredState *types.DesiredState
files []*util.File
}

func (ds *internalDesiredState) findComponent(name string) types.Component {
for _, component := range ds.desiredState.Domains[0].Components {
if component.ID == name {
return component.Component
}
}
return types.Component{}
}

// toInternalDesiredState converts incoming desired state into an internal desired state structure
func toInternalDesiredState(desiredState *types.DesiredState, domainName string) (*internalDesiredState, error) {
if len(desiredState.Domains) != 1 {
return nil, fmt.Errorf("one domain expected in desired state specification, but got %d", len(desiredState.Domains))
}
if desiredState.Domains[0].ID != domainName {
return nil, fmt.Errorf("domain id mismatch - expecting %s, received %s", domainName, desiredState.Domains[0].ID)
}
files, err := util.ToFiles(desiredState.Domains[0].Components)
if err != nil {
return nil, errors.Wrap(err, "cannot convert desired state components to container configurations")
}

return &internalDesiredState{
desiredState: desiredState,
files: files,
}, nil
}
77 changes: 77 additions & 0 deletions custom-update-agent/updateagent/update_agent_init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) 2024 Contributors to the Eclipse Foundation
//
// See the NOTICE file(s) distributed with this work for additional
// information regarding copyright ownership.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0

package updateagent

import (
"time"

"github.com/eclipse-kanto/update-manager/api"
"github.com/eclipse-kanto/update-manager/api/agent"
"github.com/eclipse-kanto/update-manager/mqtt"
)

func newUpdateAgent(
domainName string,
broker string,
keepAlive time.Duration,
disconnectTimeout time.Duration,
clientUsername string,
clientPassword string,
connectTimeout time.Duration,
acknowledgeTimeout time.Duration,
subscribeTimeout time.Duration,
unsubscribeTimeout time.Duration,
) (api.UpdateAgent, error) {

mqttClient := mqtt.NewUpdateAgentClient(domainName, &mqtt.ConnectionConfig{
Broker: broker,
KeepAlive: keepAlive.Milliseconds(),
DisconnectTimeout: disconnectTimeout.Milliseconds(),
Username: clientUsername,
Password: clientPassword,
ConnectTimeout: connectTimeout.Milliseconds(),
AcknowledgeTimeout: acknowledgeTimeout.Milliseconds(),
SubscribeTimeout: subscribeTimeout.Milliseconds(),
UnsubscribeTimeout: unsubscribeTimeout.Milliseconds(),
})

return agent.NewUpdateAgent(mqttClient, newUpdateManager(domainName)), nil
}

// newUpdateManager instantiates a new update manager instance
func newUpdateManager(domainName string) api.UpdateManager {
return &fileUpdateManager{
domainName: domainName,

createUpdateOperation: newOperation,
}
}
func Init(opts []FileUpdateAgentOpt) (interface{}, error) {
uaOpts := &updateAgentOpts{}
if err := applyOptsUpdateAgent(uaOpts, opts...); err != nil {
return nil, err
}

return newUpdateAgent(
uaOpts.domainName,
uaOpts.broker,
uaOpts.keepAlive,
uaOpts.disconnectTimeout,
uaOpts.clientUsername,
uaOpts.clientPassword,
uaOpts.connectTimeout,
uaOpts.acknowledgeTimeout,
uaOpts.subscribeTimeout,
uaOpts.unsubscribeTimeout,
)
}
Loading