2020 */
2121package eu .openanalytics .containerproxy .backend ;
2222
23- import java .io .FileInputStream ;
24- import java .io .IOException ;
25- import java .io .OutputStream ;
26- import java .nio .file .Files ;
27- import java .nio .file .Paths ;
28- import java .util .ArrayList ;
29- import java .util .Arrays ;
30- import java .util .List ;
31- import java .util .Map ;
32- import java .util .Properties ;
33- import java .util .UUID ;
34- import java .util .function .BiConsumer ;
35- import java .util .regex .Matcher ;
36- import java .util .regex .Pattern ;
37- import java .util .stream .Collectors ;
38-
39- import javax .inject .Inject ;
40-
41- import org .apache .logging .log4j .LogManager ;
42- import org .apache .logging .log4j .Logger ;
43- import org .springframework .context .annotation .Lazy ;
44- import org .springframework .core .env .Environment ;
45-
23+ import com .fasterxml .jackson .databind .MapperFeature ;
24+ import com .fasterxml .jackson .databind .ObjectMapper ;
25+ import com .fasterxml .jackson .databind .SerializationFeature ;
26+ import com .fasterxml .jackson .dataformat .yaml .YAMLFactory ;
27+ import com .google .common .base .Charsets ;
28+ import eu .openanalytics .containerproxy .ContainerProxyApplication ;
4629import eu .openanalytics .containerproxy .ContainerProxyException ;
4730import eu .openanalytics .containerproxy .auth .IAuthenticationBackend ;
4831import eu .openanalytics .containerproxy .backend .strategy .IProxyTargetMappingStrategy ;
5437import eu .openanalytics .containerproxy .service .UserService ;
5538import eu .openanalytics .containerproxy .spec .expression .ExpressionAwareContainerSpec ;
5639import eu .openanalytics .containerproxy .spec .expression .SpecExpressionResolver ;
40+ import org .apache .logging .log4j .LogManager ;
41+ import org .apache .logging .log4j .Logger ;
42+ import org .springframework .context .annotation .Lazy ;
43+ import org .springframework .core .env .Environment ;
44+
45+ import javax .inject .Inject ;
46+ import java .io .File ;
47+ import java .io .FileInputStream ;
48+ import java .io .IOException ;
49+ import java .io .OutputStream ;
50+ import java .math .BigInteger ;
51+ import java .nio .file .Files ;
52+ import java .nio .file .Paths ;
53+ import java .security .MessageDigest ;
54+ import java .security .NoSuchAlgorithmException ;
55+ import java .util .*;
56+ import java .util .function .BiConsumer ;
57+ import java .util .regex .Matcher ;
58+ import java .util .regex .Pattern ;
59+ import java .util .stream .Collectors ;
5760
5861public abstract class AbstractContainerBackend implements IContainerBackend {
5962
@@ -70,9 +73,14 @@ public abstract class AbstractContainerBackend implements IContainerBackend {
7073 protected static final String ENV_VAR_USER_GROUPS = "SHINYPROXY_USERGROUPS" ;
7174 protected static final String ENV_VAR_REALM_ID = "SHINYPROXY_REALM_ID" ;
7275
73- protected static final String LABEL_PROXY_ID = "openanalytics.eu/sp-proxy-id" ;
74- protected static final String LABEL_PROXY_SPEC_ID = "openanalytics.eu/sp-spec-id" ;
75- protected static final String LABEL_STARTUP_TIMESTAMP = "openanalytics.eu/sp-proxy-startup-timestamp" ;
76+ protected static final String RUNTIME_LABEL_PROXY_ID = "openanalytics.eu/sp-proxy-id" ;
77+ protected static final String RUNTIME_LABEL_USER_ID = "openanalytics.eu/sp-user-id" ;
78+ protected static final String RUNTIME_LABEL_USER_GROUPS = "openanalytics.eu/sp-user-groups" ;
79+ protected static final String RUNTIME_LABEL_REALM_ID = "openanalytics.eu/sp-realm-id" ;
80+ protected static final String RUNTIME_LABEL_PROXY_SPEC_ID = "openanalytics.eu/sp-spec-id" ;
81+ protected static final String RUNTIME_LABEL_STARTUP_TIMESTAMP = "openanalytics.eu/sp-proxy-startup-timestamp" ;
82+ protected static final String RUNTIME_LABEL_PROXIED_APP = "openanalytics.eu/sp-proxied-app" ;
83+ protected static final String RUNTIME_LABEL_INSTANCE = "openanalytics.eu/sp-instance" ;
7684
7785 protected final Logger log = LogManager .getLogger (getClass ());
7886
@@ -98,19 +106,31 @@ public abstract class AbstractContainerBackend implements IContainerBackend {
98106 @ Lazy
99107 // Note: lazy needed to work around early initialization conflict
100108 protected IAuthenticationBackend authBackend ;
101-
109+
110+ protected String realmId ;
111+
112+ protected String instanceId = null ;
113+
102114 @ Override
103115 public void initialize () throws ContainerProxyException {
104116 // If this application runs as a container itself, things like port publishing can be omitted.
105117 useInternalNetwork = Boolean .valueOf (getProperty (PROPERTY_INTERNAL_NETWORKING , "false" ));
106118 privileged = Boolean .valueOf (getProperty (PROPERTY_PRIVILEGED , "false" ));
119+ realmId = environment .getProperty ("proxy.realm-id" );
120+ try {
121+ instanceId = calculateInstanceId ();
122+ log .info ("Hash of config is: " + instanceId );
123+ } catch (Exception e ) {
124+ throw new RuntimeException ("Cannot compute hash of config" , e );
125+ }
107126 }
108127
109128 @ Override
110129 public void startProxy (Proxy proxy ) throws ContainerProxyException {
111130 proxy .setId (UUID .randomUUID ().toString ());
112131 proxy .setStatus (ProxyStatus .Starting );
113-
132+ proxy .setStartupTimestamp (System .currentTimeMillis ());
133+
114134 try {
115135 doStartProxy (proxy );
116136 } catch (Throwable t ) {
@@ -123,7 +143,6 @@ public void startProxy(Proxy proxy) throws ContainerProxyException {
123143 throw new ContainerProxyException ("Container did not respond in time" );
124144 }
125145
126- proxy .setStartupTimestamp (System .currentTimeMillis ());
127146 proxy .setStatus (ProxyStatus .Up );
128147 }
129148
@@ -132,19 +151,23 @@ protected void doStartProxy(Proxy proxy) throws Exception {
132151 if (authBackend != null ) authBackend .customizeContainer (spec );
133152
134153 // add labels need for App Recovery and maintenance
135- spec .addLabel (LABEL_PROXY_ID , proxy .getId ());
136- spec .addLabel (LABEL_PROXY_SPEC_ID , proxy .getSpec ().getId ());
137- spec .addLabel (LABEL_STARTUP_TIMESTAMP , String .valueOf (proxy .getStartupTimestamp ()));
154+ spec .addRuntimeLabel (RUNTIME_LABEL_PROXIED_APP , true , "true" );
155+ spec .addRuntimeLabel (RUNTIME_LABEL_INSTANCE , true , instanceId );
156+
157+ spec .addRuntimeLabel (RUNTIME_LABEL_PROXY_ID , false , proxy .getId ());
158+ spec .addRuntimeLabel (RUNTIME_LABEL_PROXY_SPEC_ID , false , proxy .getSpec ().getId ());
159+ if (realmId != null ) {
160+ spec .addRuntimeLabel (RUNTIME_LABEL_REALM_ID , false , realmId );
161+ }
162+ spec .addRuntimeLabel (RUNTIME_LABEL_USER_ID , false , proxy .getUserId ());
163+ String [] groups = userService .getGroups (userService .getCurrentAuth ());
164+ spec .addRuntimeLabel (RUNTIME_LABEL_USER_GROUPS , false , String .join ("," , groups ));
165+ spec .addRuntimeLabel (RUNTIME_LABEL_STARTUP_TIMESTAMP , false , String .valueOf (proxy .getStartupTimestamp ()));
138166
139167 ExpressionAwareContainerSpec eSpec = new ExpressionAwareContainerSpec (spec , proxy , expressionResolver );
140168 Container c = startContainer (eSpec , proxy );
141169 c .setSpec (spec );
142-
143- // remove labels needed for App Recovery since they do not really belong to the spec
144- spec .removeLabel (LABEL_PROXY_ID );
145- spec .removeLabel (LABEL_PROXY_SPEC_ID );
146- spec .removeLabel (LABEL_STARTUP_TIMESTAMP );
147-
170+
148171 proxy .getContainers ().add (c );
149172 }
150173 }
@@ -241,4 +264,40 @@ protected boolean isUseInternalNetwork() {
241264 protected boolean isPrivileged () {
242265 return privileged ;
243266 }
267+
268+
269+ /**
270+ * Calculates a hash of the config file (i.e. application.yaml).
271+ */
272+ private String calculateInstanceId () throws IOException , NoSuchAlgorithmException {
273+ /**
274+ * We need a hash of some "canonical" version of the config file.
275+ * The hash should not change when e.g. comments are added to the file.
276+ * Therefore we read the application.yml file into an Object and then
277+ * dump it again into YAML. We also sort the keys of maps and properties so that
278+ * the order does not matter for the resulting hash.
279+ */
280+ ObjectMapper objectMapper = new ObjectMapper (new YAMLFactory ());
281+ objectMapper .configure (SerializationFeature .ORDER_MAP_ENTRIES_BY_KEYS , true );
282+ objectMapper .configure (MapperFeature .SORT_PROPERTIES_ALPHABETICALLY , true );
283+
284+ File file = Paths .get (ContainerProxyApplication .CONFIG_FILENAME ).toFile ();
285+ if (!file .exists ()) {
286+ file = Paths .get (ContainerProxyApplication .CONFIG_DEMO_PROFILE ).toFile ();
287+ }
288+ if (!file .exists ()) {
289+ // this should only happen in tests
290+ instanceId = "unknown-instance-id" ;
291+ return instanceId ;
292+ }
293+
294+ Object parsedConfig = objectMapper .readValue (file , Object .class );
295+ String canonicalConfigFile = objectMapper .writeValueAsString (parsedConfig );
296+
297+ MessageDigest digest = MessageDigest .getInstance ("SHA-1" );
298+ digest .reset ();
299+ digest .update (canonicalConfigFile .getBytes (Charsets .UTF_8 ));
300+ instanceId = String .format ("%040x" , new BigInteger (1 , digest .digest ()));
301+ return instanceId ;
302+ }
244303}
0 commit comments